1 {fromCurrency} =
-
+
+ {withPopoverRecalcConfirm ? (
+
+ {exchangeRateField}
+
+ ) : (
+ exchangeRateField
+ )}
{toCurrency}
@@ -34,7 +163,7 @@ export function ExchangeRateInputGroup({
}
const ExchangeRateField = styled(FMoneyInputGroup)`
- max-width: 75px;
+ max-width: 85px;
`;
const ExchangeRateSideIcon = styled.div`
@@ -57,3 +186,8 @@ const ExchangeFlagIcon = styled(FlagIcon)`
margin-left: 5px;
display: inline-block;
`;
+
+const PopoverContent = styled('div')`
+ padding: 20px;
+ width: 300px;
+`;
diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts
index 5378efb24..c9bb52a0e 100644
--- a/packages/webapp/src/constants/dialogs.ts
+++ b/packages/webapp/src/constants/dialogs.ts
@@ -48,6 +48,7 @@ export enum DialogsName {
ProjectBillableEntriesForm = 'project-billable-entries',
InvoiceNumberSettings = 'InvoiceNumberSettings',
TaxRateForm = 'tax-rate-form',
+ InvoiceExchangeRateChangeNotice = 'InvoiceExchangeRateChangeNotice',
InvoiceMail = 'invoice-mail',
EstimateMail = 'estimate-mail',
ReceiptMail = 'receipt-mail',
diff --git a/packages/webapp/src/containers/AlertsContainer/registered.tsx b/packages/webapp/src/containers/AlertsContainer/registered.tsx
index 417583f60..d68666253 100644
--- a/packages/webapp/src/containers/AlertsContainer/registered.tsx
+++ b/packages/webapp/src/containers/AlertsContainer/registered.tsx
@@ -12,7 +12,6 @@ import PaymentMadesAlerts from '@/containers/Purchases/PaymentMades/PaymentMades
import CustomersAlerts from '@/containers/Customers/CustomersAlerts';
import VendorsAlerts from '@/containers/Vendors/VendorsAlerts';
import ManualJournalsAlerts from '@/containers/Accounting/JournalsLanding/ManualJournalsAlerts';
-import ExchangeRatesAlerts from '@/containers/ExchangeRates/ExchangeRatesAlerts';
import ExpensesAlerts from '@/containers/Expenses/ExpensesAlerts';
import AccountTransactionsAlerts from '@/containers/CashFlow/AccountTransactions/AccountTransactionsAlerts';
import UsersAlerts from '@/containers/Preferences/Users/UsersAlerts';
@@ -41,7 +40,6 @@ export default [
...CustomersAlerts,
...VendorsAlerts,
...ManualJournalsAlerts,
- ...ExchangeRatesAlerts,
...ExpensesAlerts,
...AccountTransactionsAlerts,
...UsersAlerts,
@@ -54,5 +52,5 @@ export default [
...WarehousesTransfersAlerts,
...BranchesAlerts,
...ProjectAlerts,
- ...TaxRatesAlerts
+ ...TaxRatesAlerts,
];
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.tsx
deleted file mode 100644
index 7ff0ff63d..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-// @ts-nocheck
-import * as Yup from 'yup';
-import intl from 'react-intl-universal';
-import { DATATYPES_LENGTH } from '@/constants/dataTypes';
-
-const Schema = Yup.object().shape({
- exchange_rate: Yup.number()
- .required()
- .label(intl.get('exchange_rate_')),
- currency_code: Yup.string()
- .max(3)
- .required(intl.get('currency_code_')),
- date: Yup.date()
- .required()
- .label(intl.get('date')),
-});
-
-export const CreateExchangeRateFormSchema = Schema;
-export const EditExchangeRateFormSchema = Schema;
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.tsx
deleted file mode 100644
index b0cb40518..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-// @ts-nocheck
-import React, { useMemo } from 'react';
-import intl from 'react-intl-universal';
-import moment from 'moment';
-import { Intent } from '@blueprintjs/core';
-import { Formik } from 'formik';
-import { AppToaster } from '@/components';
-import {
- CreateExchangeRateFormSchema,
- EditExchangeRateFormSchema,
-} from './ExchangeRateForm.schema';
-import ExchangeRateFormContent from './ExchangeRateFormContent';
-import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
-import withDialogActions from '@/containers/Dialog/withDialogActions';
-
-import { compose, transformToForm } from '@/utils';
-
-const defaultInitialValues = {
- exchange_rate: '',
- currency_code: '',
- date: moment(new Date()).format('YYYY-MM-DD'),
-};
-
-/**
- * Exchange rate form.
- */
-function ExchangeRateForm({
- // #withDialogActions
- closeDialog,
-}) {
- const {
- createExchangeRateMutate,
- editExchangeRateMutate,
- isNewMode,
- dialogName,
- exchangeRate,
- } = useExchangeRateFromContext();
-
- // Form validation schema in create and edit mode.
- const validationSchema = isNewMode
- ? CreateExchangeRateFormSchema
- : EditExchangeRateFormSchema;
- const initialValues = useMemo(
- () => ({
- ...defaultInitialValues,
- ...transformToForm(exchangeRate, defaultInitialValues),
- }),
- [],
- );
-
- // Transformers response errors.
- const transformErrors = (errors, { setErrors }) => {
- if (
- errors.find((error) => error.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED')
- ) {
- setErrors({
- exchange_rate: intl.get(
- 'there_is_exchange_rate_in_this_date_with_the_same_currency',
- ),
- });
- }
- };
-
- // Handle the form submit.
- const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
- setSubmitting(true);
-
- // Handle close the dialog after success response.
- const afterSubmit = () => {
- closeDialog(dialogName);
- };
- const onSuccess = ({ response }) => {
- AppToaster.show({
- message: intl.get(
- !isNewMode
- ? 'the_exchange_rate_has_been_edited_successfully'
- : 'the_exchange_rate_has_been_created_successfully',
- ),
- intent: Intent.SUCCESS,
- });
- afterSubmit(response);
- };
- // Handle the response error.
- const onError = (error) => {
- const {
- response: {
- data: { errors },
- },
- } = error;
-
- transformErrors(errors, { setErrors });
- setSubmitting(false);
- };
- if (isNewMode) {
- createExchangeRateMutate(values).then(onSuccess).catch(onError);
- } else {
- editExchangeRateMutate([exchangeRate.id, values])
- .then(onSuccess)
- .catch(onError);
- }
- };
-
- return (
-
-
-
- );
-}
-
-export default compose(withDialogActions)(ExchangeRateForm);
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.tsx
deleted file mode 100644
index 07ecc1ebc..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-import { Form } from 'formik';
-import ExchangeRateFormFields from './ExchangeRateFormFields';
-import ExchangeRateFormFooter from './ExchangeRateFormFooter';
-
-export default function ExchangeRateFormContent() {
- return (
-
- );
-}
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.tsx
deleted file mode 100644
index 2cad6e0c3..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-
-import ExchangeRateForm from './ExchangeRateForm';
-import { ExchangeRateFormProvider } from './ExchangeRateFormProvider';
-
-import '@/style/pages/ExchangeRate/ExchangeRateDialog.scss';
-
-/**
- * Exchange rate form content.
- */
-export default function ExchangeRateFormDialogContent({
- // #ownProp
- action,
- exchangeRateId,
- dialogName,
-}) {
- return (
-
-
-
- );
-}
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.tsx
deleted file mode 100644
index 58c3eb262..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-import { Classes, FormGroup, InputGroup, Position } from '@blueprintjs/core';
-import { FastField } from 'formik';
-import { DateInput } from '@blueprintjs/datetime';
-import { FormattedMessage as T } from '@/components';
-import classNames from 'classnames';
-import {
- momentFormatter,
- tansformDateValue,
- handleDateChange,
- inputIntent,
-} from '@/utils';
-import {
- ErrorMessage,
- FieldRequiredHint,
- CurrencySelectList,
-} from '@/components';
-import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
-
-
-export default function ExchangeRateFormFields() {
- const { action, currencies } = useExchangeRateFromContext();
-
- return (
-
- {/* ----------- Date ----------- */}
-
- {({ form, field: { value }, meta: { error, touched } }) => (
- }
- labelInfo={FieldRequiredHint}
- className={classNames('form-group--select-list', Classes.FILL)}
- intent={inputIntent({ error, touched })}
- helperText={}
- inline={true}
- >
- {
- form.setFieldValue('date', formattedDate);
- })}
- popoverProps={{ position: Position.BOTTOM, minimal: true }}
- disabled={action === 'edit'}
- />
-
- )}
-
- {/* ----------- Currency Code ----------- */}
-
- {({ form, field: { value }, meta: { error, touched } }) => (
- }
- labelInfo={}
- className={classNames('form-group--currency', Classes.FILL)}
- intent={inputIntent({ error, touched })}
- helperText={}
- inline={true}
- >
- {
- form.setFieldValue('currency_code', currency_code);
- }}
- disabled={action === 'edit'}
- />
-
- )}
-
-
- {/*------------ Exchange Rate -----------*/}
-
- {({ form, field, meta: { error, touched } }) => (
- }
- labelInfo={}
- intent={inputIntent({ error, touched })}
- helperText={}
- inline={true}
- >
-
-
- )}
-
-
- );
-}
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.tsx
deleted file mode 100644
index ef66f7674..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-import { useFormikContext } from 'formik';
-
-import { Button, Classes, Intent } from '@blueprintjs/core';
-import { FormattedMessage as T } from '@/components';
-import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
-import withDialogActions from '@/containers/Dialog/withDialogActions';
-import { compose } from '@/utils';
-
-function ExchangeRateFormFooter({
- // #withDialogActions
- closeDialog,
-}) {
- const { isSubmitting } = useFormikContext();
- const { dialogName, action } = useExchangeRateFromContext();
-
- const handleClose = () => {
- closeDialog(dialogName);
- };
-
- return (
-
-
-
-
-
-
- );
-}
-
-export default compose(withDialogActions)(ExchangeRateFormFooter);
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.tsx
deleted file mode 100644
index 90bdf8e96..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-// @ts-nocheck
-import React, { createContext, useContext } from 'react';
-import {
- useCreateExchangeRate,
- useEdiExchangeRate,
- useCurrencies,
- useExchangeRates,
-} from '@/hooks/query';
-import { DialogContent } from '@/components';
-
-const ExchangeRateFormContext = createContext();
-
-/**
- * Exchange rate Form page provider.
- */
-function ExchangeRateFormProvider({
- exchangeRate,
- action,
- dialogName,
- ...props
-}) {
- // Create and edit exchange rate mutations.
- const { mutateAsync: createExchangeRateMutate } = useCreateExchangeRate();
- const { mutateAsync: editExchangeRateMutate } = useEdiExchangeRate();
-
- // Load Currencies list.
- const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
- const { isFetching: isExchangeRatesLoading } = useExchangeRates();
-
- const isNewMode = !exchangeRate;
-
- // Provider state.
- const provider = {
- createExchangeRateMutate,
- editExchangeRateMutate,
- dialogName,
- exchangeRate,
- action,
- currencies,
- isExchangeRatesLoading,
- isNewMode,
- };
-
- return (
-
-
-
- );
-}
-
-const useExchangeRateFromContext = () => useContext(ExchangeRateFormContext);
-
-export { ExchangeRateFormProvider, useExchangeRateFromContext };
diff --git a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/index.tsx b/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/index.tsx
deleted file mode 100644
index 3bbc71954..000000000
--- a/packages/webapp/src/containers/Dialogs/ExchangeRateFormDialog/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-// @ts-nocheck
-import React, { lazy } from 'react';
-import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components';
-import withDialogRedux from '@/components/DialogReduxConnect';
-import { compose } from '@/utils';
-
-const ExchangeRateFormDialogContent = lazy(
- () => import('./ExchangeRateFormDialogContent'),
-);
-
-/**
- * Exchange rate form dialog.
- */
-function ExchangeRateFormDialog({
- dialogName,
- payload = { action: '', id: null, exchangeRate: '' },
- isOpen,
-}) {
- return (
-
- ) : (
-
- )
- }
- className={'dialog--exchangeRate-form'}
- isOpen={isOpen}
- autoFocus={true}
- canEscapeKeyClose={true}
- >
-
-
-
-
- );
-}
-
-export default compose(withDialogRedux())(ExchangeRateFormDialog);
diff --git a/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx
new file mode 100644
index 000000000..6554b85a1
--- /dev/null
+++ b/packages/webapp/src/containers/Entries/AutoExchangeProvider.tsx
@@ -0,0 +1,52 @@
+import { useExchangeRate } from '@/hooks/query';
+import { useCurrentOrganization } from '@/hooks/state';
+import React from 'react';
+
+interface AutoExchangeRateProviderProps {
+ children: React.ReactNode;
+}
+
+interface AutoExchangeRateProviderValue {
+ autoExRateCurrency: string;
+ isAutoExchangeRateLoading: boolean;
+}
+
+const AutoExchangeRateContext = React.createContext(
+ {} as AutoExchangeRateProviderValue,
+);
+
+function AutoExchangeRateProvider({ children }: AutoExchangeRateProviderProps) {
+ const [autoExRateCurrency, setAutoExRateCurrency] =
+ React.useState('');
+ const currentOrganization = useCurrentOrganization();
+
+ // Retrieves the exchange rate.
+ const { data: autoExchangeRate, isLoading: isAutoExchangeRateLoading } =
+ useExchangeRate(autoExRateCurrency, currentOrganization.base_currency, {
+ enabled: Boolean(currentOrganization.base_currency && autoExRateCurrency),
+ refetchOnWindowFocus: false,
+ staleTime: 0,
+ cacheTime: 0,
+ });
+
+ const value = {
+ autoExRateCurrency,
+ setAutoExRateCurrency,
+ isAutoExchangeRateLoading,
+ autoExchangeRate,
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+const useAutoExRateContext = () => React.useContext(AutoExchangeRateContext);
+
+export {
+ useAutoExRateContext,
+ AutoExchangeRateContext,
+ AutoExchangeRateProvider,
+};
diff --git a/packages/webapp/src/containers/Entries/useUpdateEntriesOnExchangeRateChange.ts b/packages/webapp/src/containers/Entries/useUpdateEntriesOnExchangeRateChange.ts
new file mode 100644
index 000000000..a4e42877c
--- /dev/null
+++ b/packages/webapp/src/containers/Entries/useUpdateEntriesOnExchangeRateChange.ts
@@ -0,0 +1,88 @@
+// @ts-nocheck
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { round } from 'lodash';
+import * as R from 'ramda';
+import { updateItemsEntriesTotal } from './utils';
+
+/**
+ * Convert the given rate to the local currency.
+ * @param {number} rate
+ * @param {number} exchangeRate
+ * @returns {number}
+ */
+export const convertToForeignCurrency = (
+ rate: number,
+ exchangeRate: number,
+) => {
+ return rate * exchangeRate;
+};
+
+/**
+ * Converts the given rate to the base currency.
+ * @param {number} rate
+ * @param {number} exchangeRate
+ * @returns {number}
+ */
+export const covertToBaseCurrency = (rate: number, exchangeRate: number) => {
+ return rate / exchangeRate;
+};
+
+/**
+ * Reverts the given rate from the old exchange rate and covert it to the new
+ * currency based on the given new exchange rate.
+ * @param {number} rate -
+ * @param {number} oldExchangeRate - Old exchange rate.
+ * @param {number} newExchangeRate - New exchange rate.
+ * @returns {number}
+ */
+const revertAndConvertExchangeRate = (
+ rate: number,
+ oldExchangeRate: number,
+ newExchangeRate: number,
+) => {
+ const oldValue = convertToForeignCurrency(rate, oldExchangeRate);
+ const newValue = covertToBaseCurrency(oldValue, newExchangeRate);
+
+ return round(newValue, 3);
+};
+
+/**
+ * Assign the new item entry rate after converting to the new exchange rate.
+ * @params {number} oldExchangeRate -
+ * @params {number} newExchangeRate -
+ * @params {IItemEntry} entries -
+ */
+const assignRateRevertAndCovertExchangeRate = R.curry(
+ (oldExchangeRate: number, newExchangeRate: number, entries: IITemEntry[]) => {
+ return entries.map((entry) => ({
+ ...entry,
+ rate: revertAndConvertExchangeRate(
+ entry.rate,
+ oldExchangeRate,
+ newExchangeRate,
+ ),
+ }));
+ },
+);
+
+/**
+ * Updates items entries on exchange rate change.
+ * @returns {(oldExchangeRate: number, newExchangeRate: number) => IItemEntry[]}
+ */
+export const useUpdateEntriesOnExchangeRateChange = () => {
+ const {
+ values: { entries },
+ } = useFormikContext();
+
+ return React.useMemo(() => {
+ return R.curry((oldExchangeRate: number, newExchangeRate: number) => {
+ return R.compose(
+ // Updates entries total.
+ updateItemsEntriesTotal,
+ // Assign a new rate of the given new exchange rate from the old exchange rate.
+ assignRateRevertAndCovertExchangeRate(oldExchangeRate, newExchangeRate),
+ )(entries);
+ });
+ }, [entries]);
+};
diff --git a/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx b/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx
new file mode 100644
index 000000000..490ef20b5
--- /dev/null
+++ b/packages/webapp/src/containers/Entries/withExRateItemEntriesPriceRecalc.tsx
@@ -0,0 +1,121 @@
+// @ts-nocheck
+import { useFormikContext } from 'formik';
+import { useUpdateEntriesOnExchangeRateChange } from './useUpdateEntriesOnExchangeRateChange';
+import { useAutoExRateContext } from './AutoExchangeProvider';
+import { useCallback, useEffect } from 'react';
+import { useCurrentOrganization } from '@/hooks/state';
+
+/**
+ * Re-calculate the item entries prices based on the old exchange rate.
+ * @param {InvoiceExchangeRateInputFieldRoot} Component
+ * @returns {JSX.Element}
+ */
+export const withExchangeRateItemEntriesPriceRecalc =
+ (Component) => (props) => {
+ const { setFieldValue } = useFormikContext();
+ const updateChangeExRate = useUpdateEntriesOnExchangeRateChange();
+
+ return (
+ {
+ setFieldValue(
+ 'entries',
+ updateChangeExRate(oldExchangeRate, exchangeRate),
+ );
+ }}
+ {...props}
+ />
+ );
+ };
+
+/**
+ * Injects the loading props to the exchange rate field.
+ * @param Component
+ * @returns {}
+ */
+export const withExchangeRateFetchingLoading = (Component) => (props) => {
+ const { isAutoExchangeRateLoading } = useAutoExRateContext();
+
+ return (
+
+ );
+};
+
+/**
+ * Updates the customer currency code and exchange rate once you update the customer
+ * then change the state to fetch the realtime exchange rate of the new selected currency.
+ */
+export const useCustomerUpdateExRate = () => {
+ const { setFieldValue, values } = useFormikContext();
+ const { setAutoExRateCurrency } = useAutoExRateContext();
+
+ const updateEntriesOnExChange = useUpdateEntriesOnExchangeRateChange();
+ const currentCompany = useCurrentOrganization();
+
+ const DEFAULT_EX_RATE = 1;
+
+ return useCallback(
+ (customer) => {
+ // Reset the auto exchange rate currency cycle.
+ setAutoExRateCurrency(null);
+
+ // If the customer's currency code equals the same base currency.
+ if (customer.currency_code === currentCompany.base_currency) {
+ setFieldValue('exchange_rate', DEFAULT_EX_RATE + '');
+ setFieldValue(
+ 'entries',
+ updateEntriesOnExChange(values.exchange_rate, DEFAULT_EX_RATE),
+ );
+ } else {
+ // Sets the currency code to fetch exchange rate of the given currency code.
+ setAutoExRateCurrency(customer?.currency_code);
+ }
+ },
+ [
+ currentCompany.base_currency,
+ setAutoExRateCurrency,
+ setFieldValue,
+ updateEntriesOnExChange,
+ values.exchange_rate,
+ ],
+ );
+};
+
+interface UseSyncExRateToFormProps {
+ onSynced?: () => void;
+}
+
+/**
+ * Syncs the realtime exchange rate to the Formik form and then re-calculates
+ * the entries rate based on the given new and old ex. rate.
+ * @param {UseSyncExRateToFormProps} props -
+ * @returns {React.ReactNode}
+ */
+export const useSyncExRateToForm = ({ onSynced }: UseSyncExRateToFormProps) => {
+ const { setFieldValue, values } = useFormikContext();
+ const { autoExRateCurrency, autoExchangeRate } = useAutoExRateContext();
+ const updateEntriesOnExChange = useUpdateEntriesOnExchangeRateChange();
+
+ // Sync the fetched real-time exchanage rate to the form.
+ useEffect(() => {
+ if (autoExchangeRate?.exchange_rate && autoExRateCurrency) {
+ setFieldValue('exchange_rate', autoExchangeRate?.exchange_rate + '');
+ setFieldValue(
+ 'entries',
+ updateEntriesOnExChange(
+ values.exchange_rate,
+ autoExchangeRate?.exchange_rate,
+ ),
+ );
+ onSynced?.();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [autoExchangeRate?.exchange_rate, autoExRateCurrency]);
+
+ return null;
+};
diff --git a/packages/webapp/src/containers/ExchangeRates/ExchangeRateActionsBar.tsx b/packages/webapp/src/containers/ExchangeRates/ExchangeRateActionsBar.tsx
deleted file mode 100644
index e2fadd8b4..000000000
--- a/packages/webapp/src/containers/ExchangeRates/ExchangeRateActionsBar.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// @ts-nocheck
-import React, { useCallback, useState, useMemo } from 'react';
-import intl from 'react-intl-universal';
-import classNames from 'classnames';
-import {
- NavbarGroup,
- NavbarDivider,
- Button,
- Classes,
- Intent,
- Popover,
- Position,
- PopoverInteractionKind,
- Alignment,
-} from '@blueprintjs/core';
-import {
- Icon,
- If,
- DashboardActionsBar,
- FormattedMessage as T,
-} from '@/components';
-import { connect } from 'react-redux';
-
-import { useRefreshExchangeRate } from '@/hooks/query/exchangeRates';
-import withDialogActions from '@/containers/Dialog/withDialogActions';
-import withResourceDetail from '@/containers/Resources/withResourceDetails';
-import withExchangeRatesActions from './withExchangeRatesActions';
-import { compose } from '@/utils';
-
-/**
- * Exchange rate actions bar.
- */
-function ExchangeRateActionsBar({
- // #withDialogActions.
- openDialog,
-
- // #withResourceDetail
- resourceFields,
-
- //#withExchangeRatesActions
- addExchangeRatesTableQueries,
-
- // #ownProps
- selectedRows = [],
- onDeleteExchangeRate,
- onFilterChanged,
- onBulkDelete,
-}) {
- const [filterCount, setFilterCount] = useState(0);
-
- const onClickNewExchangeRate = () => {
- openDialog('exchangeRate-form', {});
- };
-
- // Exchange rates refresh action.
- const { refresh } = useRefreshExchangeRate();
-
- // Handle click a refresh sale estimates
- const handleRefreshBtnClick = () => {
- refresh();
- };
-
- const hasSelectedRows = useMemo(
- () => selectedRows.length > 0,
- [selectedRows],
- );
-
- const handelBulkDelete = useCallback(() => {
- onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
- }, [onBulkDelete, selectedRows]);
-
- return (
-
-
- }
- text={}
- onClick={onClickNewExchangeRate}
- />
-
-
-
-
- ) : (
- `${filterCount} ${intl.get('filters_applied')}`
- )
- }
- icon={}
- />
-
-
-
- }
- text={}
- intent={Intent.DANGER}
- onClick={handelBulkDelete}
- />
-
-
- }
- text={}
- />
- }
- text={}
- />
-
-
- }
- onClick={handleRefreshBtnClick}
- />
-
-
- );
-}
-
-const mapStateToProps = (state, props) => ({
- resourceName: '',
-});
-
-const withExchangeRateActionBar = connect(mapStateToProps);
-
-export default compose(
- withExchangeRateActionBar,
- withDialogActions,
- withResourceDetail(({ resourceFields }) => ({
- resourceFields,
- })),
- withExchangeRatesActions,
-)(ExchangeRateActionsBar);
diff --git a/packages/webapp/src/containers/ExchangeRates/ExchangeRateTable.tsx b/packages/webapp/src/containers/ExchangeRates/ExchangeRateTable.tsx
deleted file mode 100644
index f595a26f5..000000000
--- a/packages/webapp/src/containers/ExchangeRates/ExchangeRateTable.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-// @ts-nocheck
-import React, { useCallback } from 'react';
-
-import {
- DataTable,
- TableSkeletonRows,
- TableSkeletonHeader,
-} from '@/components';
-
-import { useExchangeRatesContext } from './ExchangeRatesProvider';
-import { useExchangeRatesTableColumns, ActionMenuList } from './components';
-
-import withExchangeRates from './withExchangeRates';
-import withExchangeRatesActions from './withExchangeRatesActions';
-
-import withDialogActions from '@/containers/Dialog/withDialogActions';
-import withAlertActions from '@/containers/Alert/withAlertActions';
-import { compose } from '@/utils';
-
-/**
- * Exchange rates table.
- */
-function ExchangeRateTable({
- // #ownProps
- tableProps,
-
- // #withDialogActions.
- openDialog,
-
- // #withAlertActions
- openAlert,
-
- // #withExchangeRatesActions
- setExchangeRateTableState,
-
- // #withExchangeRates
- exchangeRatesTableState,
-}) {
- const {
- isExchangeRatesFetching,
- isExchangeRatesLoading,
-
- exchangesRates,
- pagination,
- } = useExchangeRatesContext();
-
- // Table columns.
- const columns = useExchangeRatesTableColumns();
-
- // Handle delete exchange rate.
- const handleDeleteExchangeRate = ({ id }) => {
- openAlert('exchange-rate-delete', { exchangeRateId: id });
- };
-
- // Handle Edit exchange rate.
- const handelEditExchangeRate = (exchangeRate) => {
- openDialog('exchangeRate-form', {
- action: 'edit',
- exchangeRate: exchangeRate,
- });
- };
-
- const handleFetchData = useCallback(
- ({ pageSize, pageIndex, sortBy }) => {
- setExchangeRateTableState({
- pageIndex,
- pageSize,
- sortBy,
- });
- },
- [setExchangeRateTableState],
- );
-
- return (
-
- );
-}
-
-export default compose(
- withDialogActions,
- withAlertActions,
- withExchangeRates(({ exchangeRatesTableState }) => ({
- exchangeRatesTableState,
- })),
- withExchangeRatesActions,
-)(ExchangeRateTable);
diff --git a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesAlerts.tsx b/packages/webapp/src/containers/ExchangeRates/ExchangeRatesAlerts.tsx
deleted file mode 100644
index 7ebae78c7..000000000
--- a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesAlerts.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-
-const ExchangeRateDeleteAlert = React.lazy(
- () => import('@/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert'),
-);
-
-export default [
- { name: 'exchange-rate-delete', component: ExchangeRateDeleteAlert },
-];
diff --git a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesList.tsx b/packages/webapp/src/containers/ExchangeRates/ExchangeRatesList.tsx
deleted file mode 100644
index 3f4b75ce2..000000000
--- a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesList.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// @ts-nocheck
-import React from 'react';
-
-import { DashboardContentTable, DashboardPageContent } from '@/components';
-
-import ExchangeRateTable from './ExchangeRateTable';
-import ExchangeRateActionsBar from './ExchangeRateActionsBar';
-
-import { ExchangeRatesProvider } from './ExchangeRatesProvider';
-import { transformTableStateToQuery, compose } from '@/utils';
-import withExchangeRates from './withExchangeRates';
-
-/**
- * Exchange Rates list.
- */
-function ExchangeRatesList({
- // #withExchangeRates
- exchangeRatesTableState,
-}) {
- return (
-
-
-
-
-
-
-
-
-
- );
-}
-export default compose(
- withExchangeRates(({ exchangeRatesTableState }) => ({
- exchangeRatesTableState,
- })),
-)(ExchangeRatesList);
diff --git a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesProvider.tsx b/packages/webapp/src/containers/ExchangeRates/ExchangeRatesProvider.tsx
deleted file mode 100644
index 8d1175cdc..000000000
--- a/packages/webapp/src/containers/ExchangeRates/ExchangeRatesProvider.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-// @ts-nocheck
-import React, { createContext } from 'react';
-import { transformTableQueryToParams } from '@/utils';
-
-import { DashboardInsider } from '@/components';
-import { useExchangeRates } from '@/hooks/query';
-
-const ExchangesRatesContext = createContext();
-
-/**
- * Exchanges rates list provider.
- */
-function ExchangeRatesProvider({ query, ...props }) {
- const {
- data: { exchangesRates, pagination, filterMeta },
- isFetching: isExchangeRatesFetching,
- isLoading: isExchangeRatesLoading,
- } = useExchangeRates(
- {
- ...transformTableQueryToParams(query),
- },
- { keepPreviousData: true },
- );
-
- const state = {
- isExchangeRatesFetching,
- isExchangeRatesLoading,
-
- exchangesRates,
- pagination,
- };
-
- return (
-
-
-
- );
-}
-
-const useExchangeRatesContext = () => React.useContext(ExchangesRatesContext);
-
-export { ExchangeRatesProvider, useExchangeRatesContext };
diff --git a/packages/webapp/src/containers/ExchangeRates/components.tsx b/packages/webapp/src/containers/ExchangeRates/components.tsx
deleted file mode 100644
index 2bf08f2f0..000000000
--- a/packages/webapp/src/containers/ExchangeRates/components.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-// @ts-nocheck
-import React, { useMemo } from 'react';
-import moment from 'moment';
-import intl from 'react-intl-universal';
-import {
- Menu,
- Popover,
- Button,
- Position,
- MenuItem,
- MenuDivider,
- Intent,
-} from '@blueprintjs/core';
-import { Icon, Money } from '@/components';
-import { safeCallback } from '@/utils';
-
-/**
- * Row actions menu list.
- */
-export function ActionMenuList({
- row: { original },
- payload: { onEditExchangeRate, onDeleteExchangeRate },
-}) {
- return (
-
- );
-}
-
-/**
- * Table actions cell.
- */
-export function TableActionsCell(props) {
- return (
- }
- position={Position.RIGHT_TOP}
- >
- } />
-
- );
-}
-
-export function useExchangeRatesTableColumns() {
- return useMemo(
- () => [
- {
- id: 'date',
- Header: intl.get('date'),
- accessor: (r) => moment(r.date).format('YYYY MMM DD'),
- width: 150,
- },
- {
- id: 'currency_code',
- Header: intl.get('currency_code'),
- accessor: 'currency_code',
- className: 'currency_code',
- width: 150,
- },
- {
- id: 'exchange_rate',
- Header: intl.get('exchange_rate'),
- accessor: (r) => (
-
- ),
- className: 'exchange_rate',
- width: 150,
- },
- {
- id: 'actions',
- Header: '',
- Cell: TableActionsCell,
- className: 'actions',
- width: 50,
- },
- ],
- [],
- );
-}
diff --git a/packages/webapp/src/containers/ExchangeRates/withExchangeRateDetail.tsx b/packages/webapp/src/containers/ExchangeRates/withExchangeRateDetail.tsx
deleted file mode 100644
index dc787259d..000000000
--- a/packages/webapp/src/containers/ExchangeRates/withExchangeRateDetail.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-// @ts-nocheck
-import { connect } from 'react-redux';
-import { getExchangeRateById } from '@/store/ExchangeRate/exchange.selector';
-
-const mapStateToProps = (state, props) => ({
- exchangeRate: getExchangeRateById(state, props),
-});
-
-export default connect(mapStateToProps);
diff --git a/packages/webapp/src/containers/ExchangeRates/withExchangeRates.tsx b/packages/webapp/src/containers/ExchangeRates/withExchangeRates.tsx
deleted file mode 100644
index 5636c5c25..000000000
--- a/packages/webapp/src/containers/ExchangeRates/withExchangeRates.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-// @ts-nocheck
-import { connect } from 'react-redux';
-import { getExchangeRatesTableStateFactory } from '@/store/ExchangeRate/exchange.selector';
-
-export default (mapState) => {
- const getExchangeRatesTableState = getExchangeRatesTableStateFactory();
-
- const mapStateToProps = (state, props) => {
- const mapped = {
- exchangeRatesTableState: getExchangeRatesTableState(state, props),
- };
- return mapState ? mapState(mapped, state, props) : mapped;
- };
-
- return connect(mapStateToProps);
-};
diff --git a/packages/webapp/src/containers/ExchangeRates/withExchangeRatesActions.tsx b/packages/webapp/src/containers/ExchangeRates/withExchangeRatesActions.tsx
deleted file mode 100644
index 86ddd4453..000000000
--- a/packages/webapp/src/containers/ExchangeRates/withExchangeRatesActions.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-// @ts-nocheck
-import { connect } from 'react-redux';
-import { setExchangeRateTableState } from '@/store/ExchangeRate/exchange.actions';
-
-export const mapDispatchToProps = (dispatch) => ({
- setExchangeRateTableState: (queries) =>
- dispatch(setExchangeRateTableState(queries)),
-});
-
-export default connect(null, mapDispatchToProps);
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx
index 1d51ecbc6..a52afcac4 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx
@@ -38,7 +38,10 @@ import {
import withSettings from '@/containers/Settings/withSettings';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
-import { CreditNoteSyncIncrementSettingsToForm } from './components';
+import {
+ CreditNoteExchangeRateSync,
+ CreditNoteSyncIncrementSettingsToForm,
+} from './components';
/**
* Credit note form.
@@ -169,6 +172,7 @@ function CreditNoteForm({
{/*-------- Effects --------*/}
+
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.tsx
index cd2fc1d1a..3c89f2f04 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.tsx
@@ -26,6 +26,7 @@ import {
inputIntent,
handleDateChange,
} from '@/utils';
+import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
/**
* Credit note form header fields.
@@ -37,10 +38,8 @@ export default function CreditNoteFormHeaderFields({}) {
{/* ----------- Exchange rate ----------- */}
-
+
+
{/* ----------- Credit note date ----------- */}
{({ form, field: { value }, meta: { error, touched } }) => (
@@ -93,8 +92,18 @@ export default function CreditNoteFormHeaderFields({}) {
*/
function CreditNoteCustomersSelect() {
// Credit note form context.
- const { customers } = useCreditNoteFormContext();
const { setFieldValue, values } = useFormikContext();
+ const { customers } = useCreditNoteFormContext();
+
+ const updateEntries = useCustomerUpdateExRate();
+
+ // Handles item change.
+ const handleItemChange = (customer) => {
+ setFieldValue('customer_id', customer.id);
+ setFieldValue('currency_code', customer?.currency_code);
+
+ updateEntries(customer);
+ };
return (
}
- onItemChange={(customer) => {
- setFieldValue('customer_id', customer.id);
- setFieldValue('currency_code', customer?.currency_code);
- }}
+ onItemChange={handleItemChange}
popoverFill={true}
allowCreate={true}
fastField={true}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.tsx
index bb4c7a2cd..872c2f053 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.tsx
@@ -6,6 +6,7 @@ import '@/style/pages/CreditNote/PageForm.scss';
import CreditNoteForm from './CreditNoteForm';
import { CreditNoteFormProvider } from './CreditNoteFormProvider';
+import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
/**
* Credit note form page.
@@ -16,7 +17,9 @@ export default function CreditNoteFormPage() {
return (
-
+
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx
index 2902299fa..afe139f48 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx
@@ -1,21 +1,27 @@
// @ts-nocheck
-import React, { useEffect } from 'react';
+import React, { useEffect, useRef } from 'react';
import { useFormikContext } from 'formik';
import * as R from 'ramda';
import { ExchangeRateInputGroup } from '@/components';
import { useCurrentOrganization } from '@/hooks/state';
-import { useCreditNoteIsForeignCustomer } from './utils';
+import { useCreditNoteIsForeignCustomer, useCreditNoteTotals } from './utils';
import withSettings from '@/containers/Settings/withSettings';
import { transactionNumber } from '@/utils';
+import {
+ useSyncExRateToForm,
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+} from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
+import withDialogActions from '@/containers/Dialog/withDialogActions';
+import { DialogsName } from '@/constants/dialogs';
/**
- * credit exchange rate input field.
+ * Credit note exchange rate input field.
* @returns {JSX.Element}
*/
-export function CreditNoteExchangeRateInputField({ ...props }) {
+function CreditNoteExchangeRateInputFieldRoot({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
-
const isForeignCustomer = useCreditNoteIsForeignCustomer();
// Can't continue if the customer is not foreign.
@@ -24,13 +30,21 @@ export function CreditNoteExchangeRateInputField({ ...props }) {
}
return (
);
}
+export const CreditNoteExchangeRateInputField = R.compose(
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+)(CreditNoteExchangeRateInputFieldRoot);
+
/**
* Syncs credit note auto-increment settings to form.
* @return {React.ReactNode}
@@ -56,3 +70,28 @@ export const CreditNoteSyncIncrementSettingsToForm = R.compose(
return null;
});
+
+/**
+ * Syncs the realtime exchange rate to the credit note form and shows up popup to the user
+ * as an indication the entries rates have been re-calculated.
+ * @returns {React.ReactNode}
+ */
+export const CreditNoteExchangeRateSync = R.compose(withDialogActions)(
+ ({ openDialog }) => {
+ const { total } = useCreditNoteTotals();
+ const timeout = useRef();
+
+ useSyncExRateToForm({
+ onSynced: () => {
+ // If the total bigger then zero show alert to the user after adjusting entries.
+ if (total > 0) {
+ clearTimeout(timeout.current);
+ timeout.current = setTimeout(() => {
+ openDialog(DialogsName.InvoiceExchangeRateChangeNotice);
+ }, 500);
+ }
+ },
+ });
+ return null;
+ },
+);
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx
index f9cd24673..aff0888d6 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx
@@ -1,5 +1,4 @@
// @ts-nocheck
-import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
@@ -19,7 +18,10 @@ import EstimateFloatingActions from './EstimateFloatingActions';
import EstimateFormFooter from './EstimateFormFooter';
import EstimateFormDialogs from './EstimateFormDialogs';
import EstimtaeFormTopBar from './EstimtaeFormTopBar';
-import { EstimateIncrementSyncSettingsToForm } from './components';
+import {
+ EstimateIncrementSyncSettingsToForm,
+ EstimateSyncAutoExRateToForm,
+} from './components';
import withSettings from '@/containers/Settings/withSettings';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
@@ -170,6 +172,7 @@ function EstimateForm({
{/*------- Effects -------*/}
+
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.tsx
index e3bfabb9a..eba6eb5da 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.tsx
@@ -1,5 +1,4 @@
// @ts-nocheck
-import React from 'react';
import styled from 'styled-components';
import classNames from 'classnames';
import { FormGroup, InputGroup, Position, Classes } from '@blueprintjs/core';
@@ -24,7 +23,6 @@ import {
import { customersFieldShouldUpdate } from './utils';
import { CLASSES } from '@/constants/classes';
import { Features } from '@/constants';
-
import { ProjectsSelect } from '@/containers/Projects/components';
import {
EstimateExchangeRateInputField,
@@ -32,12 +30,13 @@ import {
} from './components';
import { EstimateFormEstimateNumberField } from './EstimateFormEstimateNumberField';
import { useEstimateFormContext } from './EstimateFormProvider';
+import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
/**
* Estimate form header.
*/
export default function EstimateFormHeader() {
- const { customers, projects } = useEstimateFormContext();
+ const { projects } = useEstimateFormContext();
return (
@@ -45,10 +44,8 @@ export default function EstimateFormHeader() {
{/* ----------- Exchange Rate ----------- */}
-
+
+
{/* ----------- Estimate Date ----------- */}
{({ form, field: { value }, meta: { error, touched } }) => (
@@ -151,6 +148,16 @@ function EstimateFormCustomerSelect() {
const { setFieldValue, values } = useFormikContext();
const { customers } = useEstimateFormContext();
+ const updateEntries = useCustomerUpdateExRate();
+
+ // Handles the customer item change.
+ const handleItemChange = (customer) => {
+ setFieldValue('customer_id', customer.id);
+ setFieldValue('currency_code', customer?.currency_code);
+
+ updateEntries(customer);
+ };
+
return (
}
@@ -165,10 +172,7 @@ function EstimateFormCustomerSelect() {
name={'customer_id'}
items={customers}
placeholder={}
- onItemChange={(customer) => {
- setFieldValue('customer_id', customer.id);
- setFieldValue('currency_code', customer?.currency_code);
- }}
+ onItemChange={handleItemChange}
popoverFill={true}
allowCreate={true}
fastField={true}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormPage.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormPage.tsx
index 6f02669db..0ca5d4e37 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormPage.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormPage.tsx
@@ -6,6 +6,7 @@ import '@/style/pages/SaleEstimate/PageForm.scss';
import EstimateForm from './EstimateForm';
import { EstimateFormProvider } from './EstimateFormProvider';
+import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
/**
* Estimate form page.
@@ -16,7 +17,9 @@ export default function EstimateFormPage() {
return (
-
+
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/components.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/components.tsx
index 65a1c9cd2..3cace61f2 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/components.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/components.tsx
@@ -1,24 +1,30 @@
// @ts-nocheck
-import React, { useEffect } from 'react';
+import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import { Button } from '@blueprintjs/core';
import * as R from 'ramda';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from '@/components';
import { useCurrentOrganization } from '@/hooks/state';
-import { useEstimateIsForeignCustomer } from './utils';
-import withSettings from '@/containers/Settings/withSettings';
+import { useEstimateIsForeignCustomer, useEstimateTotals } from './utils';
import { transactionNumber } from '@/utils';
import { useUpdateEffect } from '@/hooks';
+import withSettings from '@/containers/Settings/withSettings';
+import {
+ useSyncExRateToForm,
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+} from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
+import withDialogActions from '@/containers/Dialog/withDialogActions';
+import { DialogsName } from '@/constants/dialogs';
/**
* Estimate exchange rate input field.
* @returns {JSX.Element}
*/
-export function EstimateExchangeRateInputField({ ...props }) {
+function EstimateExchangeRateInputFieldRoot({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
-
const isForeignCustomer = useEstimateIsForeignCustomer();
// Can't continue if the customer is not foreign.
@@ -27,13 +33,26 @@ export function EstimateExchangeRateInputField({ ...props }) {
}
return (
);
}
+/**
+ * Renders the estimate exchange rate input field with exchange rate
+ * with item entries price re-calc once exchange rate change.
+ * @returns {JSX.Element}
+ */
+export const EstimateExchangeRateInputField = R.compose(
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+)(EstimateExchangeRateInputFieldRoot);
+
/**
* Estimate project select.
* @returns {JSX.Element}
@@ -72,3 +91,32 @@ export const EstimateIncrementSyncSettingsToForm = R.compose(
return null;
});
+
+/**
+ * Syncs the auto exchange rate to the estimate form and shows up popup to user
+ * as an indication the entries rates have been changed.
+ * @returns {React.ReactNode}
+ */
+export const EstimateSyncAutoExRateToForm = R.compose(withDialogActions)(
+ ({
+ // #withDialogActions
+ openDialog,
+ }) => {
+ const { total } = useEstimateTotals();
+ const timeout = useRef();
+
+ useSyncExRateToForm({
+ onSynced: () => {
+ // If the total bigger then zero show alert to the user after adjusting entries.
+ if (total > 0) {
+ clearTimeout(timeout.current);
+ timeout.current = setTimeout(() => {
+ openDialog(DialogsName.InvoiceExchangeRateChangeNotice);
+ }, 500);
+ }
+ },
+ });
+
+ return null;
+ },
+);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog.tsx
new file mode 100644
index 000000000..2880ac383
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog.tsx
@@ -0,0 +1,56 @@
+// @ts-nocheck
+import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components';
+import withDialogRedux from '@/components/DialogReduxConnect';
+import withDialogActions from '@/containers/Dialog/withDialogActions';
+import { compose } from '@/utils';
+import { Button, Classes, Intent } from '@blueprintjs/core';
+
+/**
+ * Invoice number dialog.
+ */
+function InvoiceExchangeRateChangeDialog({
+ dialogName,
+ isOpen,
+ // #withDialogActions
+ closeDialog,
+}) {
+ const handleConfirm = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(
+ withDialogRedux(),
+ withDialogActions,
+)(InvoiceExchangeRateChangeDialog);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/index.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/index.ts
new file mode 100644
index 000000000..cda7f24dc
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/index.ts
@@ -0,0 +1,16 @@
+// @ts-nocheck
+import { DialogsName } from '@/constants/dialogs';
+import React from 'react';
+
+const InvoiceExchangeRateChangeAlert = React.lazy(
+ () => import('./InvoiceExchangeRateChangeDialog'),
+);
+
+const Dialogs = [
+ {
+ name: DialogsName.InvoiceExchangeRateChangeNotice,
+ component: InvoiceExchangeRateChangeAlert,
+ },
+];
+
+export default Dialogs;
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
index 3de699979..e212c8b7d 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
@@ -70,6 +70,7 @@ export default function InvoiceFloatingActions() {
history.goBack();
};
+ // Handle clear button click.
const handleClearBtnClick = (event) => {
resetForm();
};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx
index c3a447d11..6b9f234ac 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx
@@ -34,7 +34,7 @@ import {
transformValueToRequest,
resetFormState,
} from './utils';
-import { InvoiceNoSyncSettingsToForm } from './components';
+import { InvoiceExchangeRateSync, InvoiceNoSyncSettingsToForm } from './components';
/**
* Invoice form.
@@ -184,6 +184,7 @@ function InvoiceForm({
{/*---------- Effects ----------*/}
+
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.tsx
index 8697e52bc..17bd197e5 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.tsx
@@ -36,6 +36,7 @@ import {
ProjectBillableEntriesLink,
} from '@/containers/Projects/components';
import { Features } from '@/constants';
+import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
/**
* Invoice form header fields.
@@ -51,10 +52,8 @@ export default function InvoiceFormHeaderFields() {
{/* ----------- Exchange rate ----------- */}
-
+
+
{/* ----------- Invoice date ----------- */}
@@ -161,8 +160,20 @@ export default function InvoiceFormHeaderFields() {
* @returns {React.ReactNode}
*/
function InvoiceFormCustomerSelect() {
- const { customers } = useInvoiceFormContext();
const { values, setFieldValue } = useFormikContext();
+ const { customers } = useInvoiceFormContext();
+
+ const updateEntries = useCustomerUpdateExRate();
+
+ // Handles the customer item change.
+ const handleItemChange = (customer) => {
+ // If the customer id has changed change the customer id and currency code.
+ if (values.customer_id !== customer.id) {
+ setFieldValue('customer_id', customer.id);
+ setFieldValue('currency_code', customer?.currency_code);
+ }
+ updateEntries(customer);
+ };
return (
}
- onItemChange={(customer) => {
- setFieldValue('customer_id', customer.id);
- setFieldValue('currency_code', customer?.currency_code);
- }}
+ onItemChange={handleItemChange}
allowCreate={true}
fastField={true}
shouldUpdate={customerNameFieldShouldUpdate}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormPage.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormPage.tsx
index eba22308e..42190b42a 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormPage.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormPage.tsx
@@ -6,6 +6,7 @@ import '@/style/pages/SaleInvoice/PageForm.scss';
import InvoiceForm from './InvoiceForm';
import { InvoiceFormProvider } from './InvoiceFormProvider';
+import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
/**
* Invoice form page.
@@ -16,7 +17,9 @@ export default function InvoiceFormPage() {
return (
-
+
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/components.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/components.tsx
index 0020a7e8d..788cd2c5c 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/components.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/components.tsx
@@ -1,24 +1,30 @@
// @ts-nocheck
-import React from 'react';
+import { useRef } from 'react';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { Button } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from '@/components';
import { useCurrentOrganization } from '@/hooks/state';
-import { useInvoiceIsForeignCustomer } from './utils';
+import { useInvoiceIsForeignCustomer, useInvoiceTotal } from './utils';
import withSettings from '@/containers/Settings/withSettings';
import { useUpdateEffect } from '@/hooks';
import { transactionNumber } from '@/utils';
+import withDialogActions from '@/containers/Dialog/withDialogActions';
+import { DialogsName } from '@/constants/dialogs';
+import {
+ useSyncExRateToForm,
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+} from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
/**
* Invoice exchange rate input field.
* @returns {JSX.Element}
*/
-export function InvoiceExchangeRateInputField({ ...props }) {
+const InvoiceExchangeRateInputFieldRoot = ({ ...props }) => {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
-
const isForeignCustomer = useInvoiceIsForeignCustomer();
// Can't continue if the customer is not foreign.
@@ -27,12 +33,24 @@ export function InvoiceExchangeRateInputField({ ...props }) {
}
return (
);
-}
+};
+
+/**
+ * Invoice exchange rate input field.
+ * @returns {JSX.Element}
+ */
+export const InvoiceExchangeRateInputField = R.compose(
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+)(InvoiceExchangeRateInputFieldRoot);
/**
* Invoice project select.
@@ -66,3 +84,28 @@ export const InvoiceNoSyncSettingsToForm = R.compose(
return null;
});
+
+/**
+ * Syncs the realtime exchange rate to the invoice form and shows up popup to the user
+ * as an indication the entries rates have been re-calculated.
+ * @returns {React.ReactNode}
+ */
+export const InvoiceExchangeRateSync = R.compose(withDialogActions)(
+ ({ openDialog }) => {
+ const total = useInvoiceTotal();
+ const timeout = useRef();
+
+ useSyncExRateToForm({
+ onSynced: () => {
+ // If the total bigger then zero show alert to the user after adjusting entries.
+ if (total > 0) {
+ clearTimeout(timeout.current);
+ timeout.current = setTimeout(() => {
+ openDialog(DialogsName.InvoiceExchangeRateChangeNotice);
+ }, 500);
+ }
+ },
+ });
+ return null;
+ },
+);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
index 7b8a22814..8c32f017c 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
@@ -5,7 +5,7 @@ import intl from 'react-intl-universal';
import moment from 'moment';
import * as R from 'ramda';
import { Intent } from '@blueprintjs/core';
-import { omit, first, sumBy } from 'lodash';
+import { omit, first, sumBy, round } from 'lodash';
import {
compose,
transformToForm,
@@ -57,7 +57,7 @@ export const defaultInvoice = {
reference_no: '',
invoice_message: '',
terms_conditions: '',
- exchange_rate: 1,
+ exchange_rate: '1',
currency_code: '',
branch_id: '',
warehouse_id: '',
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx
index ca7dd26f6..62206bdbf 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx
@@ -34,7 +34,7 @@ import {
transformFormValuesToRequest,
resetFormState,
} from './utils';
-import { ReceiptSyncIncrementSettingsToForm } from './components';
+import { ReceiptSyncAutoExRateToForm, ReceiptSyncIncrementSettingsToForm } from './components';
/**
* Receipt form.
@@ -171,6 +171,7 @@ function ReceiptForm({
{/*---------- Effects ---------*/}
+
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.tsx
index 92ef297cb..1536c9785 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.tsx
@@ -33,6 +33,7 @@ import {
ReceiptProjectSelectButton,
} from './components';
import { ReceiptFormReceiptNumberField } from './ReceiptFormReceiptNumberField';
+import { useCustomerUpdateExRate } from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
/**
* Receipt form header fields.
@@ -46,10 +47,7 @@ export default function ReceiptFormHeader() {
{/* ----------- Exchange rate ----------- */}
-
+
{/* ----------- Deposit account ----------- */}
{
+ setFieldValue('customer_id', customer.id);
+ setFieldValue('currency_code', customer?.currency_code);
+
+ updateEntries(customer);
+ };
+
return (
}
- onItemChange={(customer) => {
- setFieldValue('customer_id', customer.id);
- setFieldValue('currency_code', customer?.currency_code);
- }}
+ onItemChange={handleItemChange}
popoverFill={true}
allowCreate={true}
fastField={true}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormPage.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormPage.tsx
index dddd093ab..da66da72b 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormPage.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormPage.tsx
@@ -6,6 +6,7 @@ import '@/style/pages/SaleReceipt/PageForm.scss';
import ReceiptFrom from './ReceiptForm';
import { ReceiptFormProvider } from './ReceiptFormProvider';
+import { AutoExchangeRateProvider } from '@/containers/Entries/AutoExchangeProvider';
/**
* Receipt form page.
@@ -16,7 +17,9 @@ export default function ReceiptFormPage() {
return (
-
+
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/components.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/components.tsx
index 937a4f937..7d47998e3 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/components.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/components.tsx
@@ -1,5 +1,5 @@
// @ts-nocheck
-import React from 'react';
+import React, { useRef } from 'react';
import intl from 'react-intl-universal';
import { Button } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
@@ -7,20 +7,26 @@ import * as R from 'ramda';
import { ExchangeRateInputGroup } from '@/components';
import { useCurrentOrganization } from '@/hooks/state';
-import { useReceiptIsForeignCustomer } from './utils';
+import { useReceiptIsForeignCustomer, useReceiptTotals } from './utils';
import { useUpdateEffect } from '@/hooks';
-import withSettings from '@/containers/Settings/withSettings';
import { transactionNumber } from '@/utils';
+import withSettings from '@/containers/Settings/withSettings';
+import {
+ useSyncExRateToForm,
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+} from '@/containers/Entries/withExRateItemEntriesPriceRecalc';
+import withDialogActions from '@/containers/Dialog/withDialogActions';
+import { DialogsName } from '@/constants/dialogs';
/**
* Receipt exchange rate input field.
* @returns {JSX.Element}
*/
-export function ReceiptExchangeRateInputField({ ...props }) {
+function ReceiptExchangeRateInputFieldRoot({ ...props }) {
const currentOrganization = useCurrentOrganization();
- const { values } = useFormikContext();
-
const isForeignCustomer = useReceiptIsForeignCustomer();
+ const { values } = useFormikContext();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
@@ -28,13 +34,21 @@ export function ReceiptExchangeRateInputField({ ...props }) {
}
return (
);
}
+export const ReceiptExchangeRateInputField = R.compose(
+ withExchangeRateFetchingLoading,
+ withExchangeRateItemEntriesPriceRecalc,
+)(ReceiptExchangeRateInputFieldRoot);
+
/**
* Receipt project select.
* @returns {JSX.Element}
@@ -73,3 +87,31 @@ export const ReceiptSyncIncrementSettingsToForm = R.compose(
return null;
});
+
+/**
+ * Syncs the auto exchange rate to the receipt form and shows up popup to user
+ * as an indication the entries rates have been changed.
+ * @returns {React.ReactNode}
+ */
+export const ReceiptSyncAutoExRateToForm = R.compose(withDialogActions)(
+ ({
+ // #withDialogActions
+ openDialog,
+ }) => {
+ const { total } = useReceiptTotals();
+ const timeout = useRef();
+
+ useSyncExRateToForm({
+ onSynced: () => {
+ // If the total bigger then zero show alert to the user after adjusting entries.
+ if (total > 0) {
+ clearTimeout(timeout.current);
+ timeout.current = setTimeout(() => {
+ openDialog(DialogsName.InvoiceExchangeRateChangeNotice);
+ }, 500);
+ }
+ },
+ });
+ return null;
+ },
+);
diff --git a/packages/webapp/src/hooks/query/exchangeRates.tsx b/packages/webapp/src/hooks/query/exchangeRates.tsx
index f38b66737..f56958040 100644
--- a/packages/webapp/src/hooks/query/exchangeRates.tsx
+++ b/packages/webapp/src/hooks/query/exchangeRates.tsx
@@ -1,102 +1,34 @@
// @ts-nocheck
-import { useMutation, useQueryClient } from 'react-query';
-import { defaultTo } from 'lodash';
-import { useQueryTenant } from '../useQueryRequest';
-import { transformPagination } from '@/utils';
-import useApiRequest from '../useRequest';
+import { useQuery } from 'react-query';
+import QUERY_TYPES from './types';
-const defaultPagination = {
- pageSize: 20,
- page: 0,
- pagesCount: 0,
-};
-/**
- * Creates a new exchange rate.
- */
-export function useCreateExchangeRate(props) {
- const queryClient = useQueryClient();
- const apiRequest = useApiRequest();
-
- return useMutation((values) => apiRequest.post('exchange_rates', values), {
- onSuccess: () => {
- queryClient.invalidateQueries('EXCHANGES_RATES');
- },
- ...props,
- });
+function getRandomItemFromArray(arr) {
+ const randomIndex = Math.floor(Math.random() * arr.length);
+ return arr[randomIndex];
+}
+function delay(t, val) {
+ return new Promise((resolve) => setTimeout(resolve, t, val));
}
-
/**
- * Edits the exchange rate.
+ * Retrieves tax rates.
+ * @param {number} customerId - Customer id.
*/
-export function useEdiExchangeRate(props) {
- const queryClient = useQueryClient();
- const apiRequest = useApiRequest();
+export function useExchangeRate(
+ fromCurrency: string,
+ toCurrency: string,
+ props,
+) {
+ return useQuery(
+ [QUERY_TYPES.EXCHANGE_RATE, fromCurrency, toCurrency],
+ async () => {
+ await delay(100);
- return useMutation(
- ([id, values]) => apiRequest.post(`exchange_rates/${id}`, values),
- {
- onSuccess: () => {
- queryClient.invalidateQueries('EXCHANGES_RATES');
- },
- ...props,
+ return {
+ from_currency: fromCurrency,
+ to_currency: toCurrency,
+ exchange_rate: 1.00,
+ };
},
+ props,
);
}
-
-/**
- * Deletes the exchange rate.
- */
-export function useDeleteExchangeRate(props) {
- const queryClient = useQueryClient();
- const apiRequest = useApiRequest();
-
- return useMutation((id) => apiRequest.delete(`exchange_rates/${id}`), {
- onSuccess: () => {
- queryClient.invalidateQueries('EXCHANGES_RATES');
- },
- ...props,
- });
-}
-
-/**
- * Retrieve the exchange rate list.
- */
-export function useExchangeRates(query, props) {
- const apiRequest = useApiRequest();
-
- const states = useQueryTenant(
- ['EXCHANGES_RATES', query],
- () => apiRequest.get('exchange_rates', { params: query }),
- {
- select: (res) => ({
- exchangesRates: res.data.exchange_rates.results,
- pagination: transformPagination(res.data.exchange_rates.pagination),
- filterMeta: res.data.filter_meta,
- }),
- ...props,
- },
- );
-
- return {
- ...states,
- data: defaultTo(states.data, {
- exchangesRates: [],
- pagination: {
- page: 1,
- pageSize: 20,
- total: 0,
- },
- filterMeta: {},
- }),
- };
-}
-
-export function useRefreshExchangeRate() {
- const queryClient = useQueryClient();
-
- return {
- refresh: () => {
- queryClient.invalidateQueries('EXCHANGES_RATES');
- },
- };
-}
diff --git a/packages/webapp/src/hooks/query/types.tsx b/packages/webapp/src/hooks/query/types.tsx
index 0a10b1e63..9d3446c77 100644
--- a/packages/webapp/src/hooks/query/types.tsx
+++ b/packages/webapp/src/hooks/query/types.tsx
@@ -32,7 +32,7 @@ const FINANCIAL_REPORTS = {
REALIZED_GAIN_OR_LOSS: 'REALIZED_GAIN_OR_LOSS',
UNREALIZED_GAIN_OR_LOSS: 'UNREALIZED_GAIN_OR_LOSS',
PROJECT_PROFITABILITY_SUMMARY: 'PROJECT_PROFITABILITY_SUMMARY',
- SALES_TAX_LIABILITY_SUMMARY: 'SALES_TAX_LIABILITY_SUMMARY'
+ SALES_TAX_LIABILITY_SUMMARY: 'SALES_TAX_LIABILITY_SUMMARY',
};
const BILLS = {
@@ -226,12 +226,17 @@ const DASHBOARD = {
};
const ORGANIZATION = {
- ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES: 'ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES',
+ ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES:
+ 'ORGANIZATION_MUTATE_BASE_CURRENCY_ABILITIES',
};
export const TAX_RATES = {
TAX_RATES: 'TAX_RATES',
-}
+};
+
+export const EXCHANGE_RATE = {
+ EXCHANGE_RATE: 'EXCHANGE_RATE',
+};
export default {
...Authentication,
@@ -266,5 +271,6 @@ export default {
...BRANCHES,
...DASHBOARD,
...ORGANIZATION,
- ...TAX_RATES
+ ...TAX_RATES,
+ ...EXCHANGE_RATE,
};
diff --git a/packages/webapp/src/routes/dashboard.tsx b/packages/webapp/src/routes/dashboard.tsx
index 5193c63f8..c1137bc5b 100644
--- a/packages/webapp/src/routes/dashboard.tsx
+++ b/packages/webapp/src/routes/dashboard.tsx
@@ -473,16 +473,6 @@ export const getDashboardRoutes = () => [
pageTitle: intl.get('all_financial_reports'),
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
- // Exchange Rates
- // {
- // path: `/exchange-rates`,
- // component: lazy(
- // () => import('@/containers/ExchangeRates/ExchangeRatesList'),
- // ),
- // breadcrumb: intl.get('exchange_rates_list'),
- // pageTitle: intl.get('exchange_rates_list'),
- // subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
- // },
// Expenses.
{
path: `/expenses/new`,