diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index f671e349b..3b4b4a595 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -27,6 +27,8 @@ const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); const CompressionPlugin = require("compression-webpack-plugin"); const postcssNormalize = require('postcss-normalize'); +const { postcssRTLCSS} = require('postcss-rtlcss'); + const appPackageJson = require(paths.appPackageJson); @@ -92,6 +94,14 @@ module.exports = function(webpackEnv) { // https://github.com/facebook/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ + // Postcss rtlcss plugin. + // require( 'postcss-rtl' )({ + // // options here. + // removeComments: false, + // }), + postcssRTLCSS({ + + }), require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { @@ -120,6 +130,10 @@ module.exports = function(webpackEnv) { loader: require.resolve(preProcessor), options: { sourceMap: true, + sassOptions: { + sourceComments: true, + outputStyle: 'expanded' + } }, } ); diff --git a/client/package.json b/client/package.json index 61f6187e5..a0b40b717 100644 --- a/client/package.json +++ b/client/package.json @@ -61,7 +61,9 @@ "postcss-loader": "3.0.0", "postcss-normalize": "8.0.1", "postcss-preset-env": "6.7.0", + "postcss-rtl": "^1.7.3", "postcss-safe-parser": "4.0.1", + "ramda": "^0.27.1", "react": "^16.12.0", "react-app-polyfill": "^1.0.6", "react-body-classname": "^1.3.1", @@ -71,7 +73,7 @@ "react-dropzone": "^11.0.1", "react-error-boundary": "^3.0.2", "react-hotkeys-hook": "^3.0.3", - "react-intl": "^3.12.0", + "react-intl-universal": "^2.4.7", "react-loadable": "^5.5.0", "react-query": "^3.6.0", "react-redux": "^7.1.3", @@ -92,6 +94,7 @@ "redux-thunk": "^2.3.0", "resolve": "1.15.0", "resolve-url-loader": "3.1.1", + "rtl-detect": "^1.0.3", "sass-loader": "8.0.2", "semver": "6.3.0", "style-loader": "0.23.1", @@ -135,6 +138,7 @@ "@welldone-software/why-did-you-render": "^6.0.0-rc.1", "compression-webpack-plugin": "^6.1.0", "http-proxy-middleware": "^1.0.0", + "postcss-rtlcss": "^1.7.2", "react-query-devtools": "^2.1.1", "redux-devtools": "^3.5.0", "typescript": "^4.1.2" diff --git a/client/public/index.html b/client/public/index.html index 35b8d3acb..9a52dc7d6 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -1,5 +1,5 @@ - + diff --git a/client/src/common/adjustmentType.js b/client/src/common/adjustmentType.js index f2239f6f8..8db0f29e1 100644 --- a/client/src/common/adjustmentType.js +++ b/client/src/common/adjustmentType.js @@ -1,4 +1,6 @@ +import intl from 'react-intl-universal'; + export default [ - { name: 'Decrement', value: 'decrement' }, - { name: 'Increment', value: 'increment' }, + { name: intl.get('decrement'), value: 'decrement' }, + { name: intl.get('increment'), value: 'increment' }, ] \ No newline at end of file diff --git a/client/src/common/contactsOptions.js b/client/src/common/contactsOptions.js index 0cbe1a5ed..f03a49e22 100644 --- a/client/src/common/contactsOptions.js +++ b/client/src/common/contactsOptions.js @@ -1,4 +1,7 @@ +import React from 'react'; +import intl from 'react-intl-universal'; + export default [ - { name: 'Customer', path: 'customers' }, - { name: 'Vendor', path: 'vendors' }, + { name: intl.get('customer'), path: 'customers' }, + { name: intl.get('vendor'), path: 'vendors' }, ]; diff --git a/client/src/common/countries.js b/client/src/common/countries.js index cf08fcf48..c86568887 100644 --- a/client/src/common/countries.js +++ b/client/src/common/countries.js @@ -1,5 +1,3 @@ +import intl from 'react-intl-universal'; - -export default [ - { name: 'Libya', value: 'libya' }, -] \ No newline at end of file +export default [{ name: intl.get('libya'), value: 'libya' }]; diff --git a/client/src/common/currencies.js b/client/src/common/currencies.js index 8e9ffd911..ae1dbd6d9 100644 --- a/client/src/common/currencies.js +++ b/client/src/common/currencies.js @@ -1,5 +1,7 @@ +import intl from 'react-intl-universal'; + export default [ - { name: 'US Dollar', code: 'USD' }, - { name: 'Euro', code: 'EUR' }, - { name: 'Libyan Dinar ', code: 'LYD' }, -] \ No newline at end of file + { name: intl.get('us_dollar'), code: 'USD' }, + { name: intl.get('euro'), code: 'EUR' }, + { name: intl.get('libyan_diner'), code: 'LYD' }, +]; diff --git a/client/src/common/dateFormatsOptions.js b/client/src/common/dateFormatsOptions.js index bed2cda21..8c6ed99a2 100644 --- a/client/src/common/dateFormatsOptions.js +++ b/client/src/common/dateFormatsOptions.js @@ -1,40 +1,41 @@ import moment from 'moment'; +import intl from 'react-intl-universal'; export default [ { id: 1, - name: 'MM/DD/YY', + name: intl.get('mm_dd_yy'), label: `${moment().format('MM/DD/YYYY')}`, value: 'mm/dd/yy', }, { id: 2, - name: 'DD/MM/YY', + name: intl.get('dd_mm_yy'), label: `${moment().format('DD/MM/YYYY')}`, value: 'dd/mm/yy', }, { id: 3, - name: 'YY/MM/DD', + name: intl.get('yy_mm_dd'), label: `${moment().format('YYYY/MM/DD')}`, value: 'yy/mm/dd', }, { id: 4, - name: 'MM-DD-YY', + name: intl.get('mm_dd_yy'), label: `${moment().format('MM-DD-YYYY')}`, value: 'mm-dd-yy', }, { id: 5, - name: 'DD-MM-YY', + name: intl.get('dd_mm_yy_'), label: `${moment().format('DD-MM-YYYY')}`, value: 'dd-mm-yy', }, { id: 6, - name: 'YY-MM-DD', + name: intl.get('yy_mm_dd_'), label: `${moment().format('YYYY-MM-DD')}`, value: 'yy-mm-dd', }, -] \ No newline at end of file +]; diff --git a/client/src/common/fiscalYearOptions.js b/client/src/common/fiscalYearOptions.js index 1a89aabb7..24043ec80 100644 --- a/client/src/common/fiscalYearOptions.js +++ b/client/src/common/fiscalYearOptions.js @@ -1,88 +1,64 @@ -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; -export default [ +export const getFiscalYearOptions = () => [ { id: 0, - name: `${formatMessage({ id: 'january' })} - ${formatMessage({ - id: 'december', - })}`, + name: `${intl.get('january')} - ${intl.get('december')}`, value: 'january', }, { id: 1, - name: `${formatMessage({ id: 'february' })} - ${formatMessage({ - id: 'january', - })}`, + name: `${intl.get('february')} - ${intl.get('january')}`, value: 'february', }, { id: 2, - name: `${formatMessage({ id: 'march' })} - ${formatMessage({ - id: 'february', - })}`, + name: `${intl.get('march')} - ${intl.get('february')}`, value: 'March', }, { id: 3, - name: `${formatMessage({ id: 'april' })} - ${formatMessage({ - id: 'march', - })}`, + name: `${intl.get('april')} - ${intl.get('march')}`, value: 'april', }, { id: 4, - name: `${formatMessage({ id: 'may' })} - ${formatMessage({ - id: 'april', - })}`, + name: `${intl.get('may')} - ${intl.get('april')}`, value: 'may', }, { id: 5, - name: `${formatMessage({ id: 'june' })} - ${formatMessage({ - id: 'may', - })}`, + name: `${intl.get('june')} - ${intl.get('may')}`, value: 'june', }, { id: 6, - name: `${formatMessage({ id: 'july' })} - ${formatMessage({ - id: 'june', - })}`, + name: `${intl.get('july')} - ${intl.get('june')}`, value: 'july', }, { id: 7, - name: `${formatMessage({ id: 'august' })} - ${formatMessage({ - id: 'july', - })}`, + name: `${intl.get('august')} - ${intl.get('july')}`, value: 'August', }, { id: 8, - name: `${formatMessage({ id: 'september' })} - ${formatMessage({ - id: 'august', - })}`, + name: `${intl.get('september')} - ${intl.get('august')}`, value: 'september', }, { id: 9, - name: `${formatMessage({ id: 'october' })} - ${formatMessage({ - id: 'november', - })}`, + name: `${intl.get('october')} - ${intl.get('november')}`, value: 'october', }, { id: 10, - name: `${formatMessage({ id: 'november' })} - ${formatMessage({ - id: 'october', - })}`, + name: `${intl.get('november')} - ${intl.get('october')}`, value: 'november', }, { id: 11, - name: `${formatMessage({ id: 'december' })} - ${formatMessage({ - id: 'november', - })}`, + name: `${intl.get('december')} - ${intl.get('november')}`, value: 'december', }, ] \ No newline at end of file diff --git a/client/src/common/homepageOptions.js b/client/src/common/homepageOptions.js index 681ceef20..82972db3a 100644 --- a/client/src/common/homepageOptions.js +++ b/client/src/common/homepageOptions.js @@ -1,38 +1,35 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; export const accountsReceivable = [ { sectionTitle: , shortcuts: [ { - title: 'Sales invoices', - description: 'Tracking sales invoices with your customers with payment due date.', + title: , + description: , link: '/invoices', }, { - title: 'Sales estimates', - description: - 'Manage your sales estimates to create quotes that can later be turned to a sale invoice.', - + title: , + description: , link: '/estimates', }, { - title: 'Sales receipts', - description: - 'Manage sales receipts for sales that get paid immediately from the customer.', - + title: , + description: , link: '/receipts', }, { - title: 'Customers', - description: 'Manage the customers relations with customer receivable and credit balances.', + title: , + description: , link: '/customers', }, { - title: 'Customers payment', - description: - 'Manage payment transactions from your customers with sale invoices.', + title: , + description: ( + + ), link: '/payment-receives', }, ], @@ -44,20 +41,22 @@ export const accountsPayable = [ sectionTitle: , shortcuts: [ { - title: 'Purchase invoices', - description: 'Manage the purchase invoices with your vendors with payment due date.', + title: , + description: ( + + ), link: '/bills', }, { - title: 'Vendors', - description: 'Manage the vendors relations with vendor payable and debit balances.', + title: , + description: ( + + ), link: '/vendors', }, { - title: 'Vendors payments', - description: - 'Manage payments transactions to your vendors with purchase invoices.', - + title: , + description: , link: '/payment-mades', }, ], @@ -69,26 +68,29 @@ export const financialAccounting = [ sectionTitle: , shortcuts: [ { - title: 'Chart of accounts', - description: - 'Manage your accounts chart to record your transactions and categorise your transactions in parent accounts.', + title: , + description: ( + + ), link: '/accounts', }, { - title: 'Manual journal', - description: 'Manage manual journal transactions on accounts, cost centra and projects.', + title: , + description:, link: '/manual-journals', }, { - title: 'Expenses', - description: - 'Track your indirect expenses under specific categories such as payroll, rent.', + title: , + description:, link: '/expenses', }, { - title: 'Financial statements', - description: - 'Show financial reports about your organization to summarize your business’s financial performance.', + title: , + description:, link: '/financial-reports', }, ], @@ -100,20 +102,18 @@ export const productsServices = [ sectionTitle: , shortcuts: [ { - title: 'Products & Services', - description: - 'Manage your products (inventory or non-inventory) and services and place them into categories.', + title: , + description:, link: '/items', }, { - title: 'Products & Services Categories', - description: - 'Group your products and service into different categories.', + title: , + description:, link: 'items/categories', }, { - title: 'Inventory Adjustments', - description: 'Manage your inventory adjustment of inventory items.', + title: , + description: , link: '/inventory-adjustments', }, ], diff --git a/client/src/common/keyboardShortcutsOptions.js b/client/src/common/keyboardShortcutsOptions.js index 8bd1efd33..c33e5a705 100644 --- a/client/src/common/keyboardShortcutsOptions.js +++ b/client/src/common/keyboardShortcutsOptions.js @@ -1,102 +1,105 @@ +import React from 'react'; +import intl from 'react-intl-universal'; + export default [ { shortcut_key: 'Shift + I', - description: 'Jump to the invoices.', + description: intl.get('jump_to_the_invoices'), }, { shortcut_key: 'Shift + E', - description: 'Jump to the estimates.', + description: intl.get('jump_to_the_estimates'), }, { shortcut_key: 'Shift + R', - description: 'Jump to the receipts.', + description: intl.get('jump_to_the_receipts'), }, { shortcut_key: 'Shift + X', - description: 'Jump to the expenses.', + description: intl.get('jump_to_the_expenses'), }, { shortcut_key: 'Shift + C', - description: 'Jump to the customers.', + description: intl.get('jump_to_the_customers'), }, { shortcut_key: 'Shift + V', - description: 'Jump to the vendors.', + description: intl.get('jump_to_the_vendors'), }, { shortcut_key: 'Shift + A', - description: 'Jump to the Chart of Accounts.', + description: intl.get('jump_to_the_chart_of_accounts'), }, { shortcut_key: 'Shift + B', - description: 'Jump to the bills.', + description: intl.get('jump_to_the_bills'), }, { shortcut_key: 'Shift + M', - description: 'Jump to the Manual Journals.', + description: intl.get('jump_to_the_manual_journals'), }, { shortcut_key: 'Shift + W', - description: 'Jump to the items.', + description: intl.get('jump_to_the_items'), }, { shortcut_key: 'Shift + 1', - description: 'Jump to the Balance Sheet.', + description: intl.get('jump_to_the_balance_sheet'), }, { shortcut_key: 'Shift + 2', - description: 'Jump to the Profit Loss Sheet.', + description: intl.get('jump_to_the_profit_loss_sheet'), }, { shortcut_key: 'Shift + 3', - description: 'Jump to the Journal Sheet.', + description: intl.get('jump_to_the_journal_sheet'), }, { shortcut_key: 'Shift + 4', - description: 'Jump to the General Ledger Sheet.', + description: intl.get('jump_to_the_general_ledger_sheet'), }, { shortcut_key: 'Shift + 5', - description: 'Jump to the Trial Balance Sheet.', + description: intl.get('jump_to_the_trial_balance_sheet'), }, { shortcut_key: 'Ctrl + Shift + I ', - description: 'Create a new invoice .', + description: intl.get('create_a_new_invoice'), }, { shortcut_key: 'Ctrl + Shift + E ', - description: 'Create a new estimate .', + description: intl.get('create_a_new_estimate'), }, { shortcut_key: 'Ctrl + Shift + R ', - description: 'Create a new receipt .', + description: intl.get('create_a_new_receipt'), }, { shortcut_key: 'Ctrl + Shift + X ', - description: 'Create a new expense.', + description: intl.get('create_a_new_expense'), }, { shortcut_key: 'Ctrl + Shift + C ', - description: 'Create a new customer.', + description: intl.get('create_a_new_customer'), }, { shortcut_key: 'Ctrl + Shift + V ', - description: 'Create a new vendor.', + description: intl.get('create_a_new_vendor'), }, { shortcut_key: 'Ctrl + Shift + B ', - description: 'Create a new bill.', + description: intl.get('create_a_new_bill'), }, { shortcut_key: 'Ctrl + Shift + M ', - description: 'Create a new Make Journal.', + description: intl.get('create_a_new_journal'), }, { shortcut_key: 'Ctrl + Shift + W ', - description: 'Create a new item.', + description: intl.get('create_a_new_item'), }, { shortcut_key: 'Ctrl + / ', - description: 'Close and open sidebar .', + description: intl.get('close_and_open_sidebar'), }, ]; diff --git a/client/src/common/languagesOptions.js b/client/src/common/languagesOptions.js index b331d32ec..77ddb2f6b 100644 --- a/client/src/common/languagesOptions.js +++ b/client/src/common/languagesOptions.js @@ -1,6 +1,6 @@ - +import intl from 'react-intl-universal'; export default [ - { name: 'English', value: 'en' }, - { name: 'Arabic', value: 'ar' }, -]; \ No newline at end of file + { name: intl.get('english'), value: 'en' }, + { name: intl.get('arabic'), value: 'ar' }, +]; diff --git a/client/src/common/numberFormatsOptions.js b/client/src/common/numberFormatsOptions.js index 0315eae93..4e2b6ccf8 100644 --- a/client/src/common/numberFormatsOptions.js +++ b/client/src/common/numberFormatsOptions.js @@ -1,7 +1,9 @@ +import intl from 'react-intl-universal'; + export const moneyFormat = [ - { key: 'total', text: 'Total rows' }, - { key: 'always', text: 'Always' }, - { key: 'none', text: 'None' }, + { key: 'total', text: intl.get('total_rows') }, + { key: 'always', text: intl.get('always') }, + { key: 'none', text: intl.get('none') }, ]; export const negativeFormat = [ diff --git a/client/src/common/quickNewOptions.js b/client/src/common/quickNewOptions.js index a5a9efd68..6ebb1ec57 100644 --- a/client/src/common/quickNewOptions.js +++ b/client/src/common/quickNewOptions.js @@ -1,8 +1,10 @@ -export default [ - { path: 'invoices/new', name: 'Sale invoice' }, - { path: 'bills//new', name: 'Purchase invoice' }, - { path: 'make-journal-entry', name: 'Manual journal' }, - { path: 'expenses/new', name: 'Expense' }, - { path: 'customers/new', name: 'Customer' }, - { path: 'vendors/new', name: 'Vendor' }, +import intl from 'react-intl-universal'; + +export const getQuickNewActions = () => [ + { path: 'invoices/new', name: intl.get('sale_invoice') }, + { path: 'bills//new', name: intl.get('purchase_invoice') }, + { path: 'make-journal-entry', name: intl.get('manual_journal') }, + { path: 'expenses/new', name: intl.get('expense') }, + { path: 'customers/new', name: intl.get('customer') }, + { path: 'vendors/new', name: intl.get('vendor') }, ]; diff --git a/client/src/common/registerWizard.js b/client/src/common/registerWizard.js index 634f2271a..900822f01 100644 --- a/client/src/common/registerWizard.js +++ b/client/src/common/registerWizard.js @@ -1,16 +1,16 @@ +import intl from 'react-intl-universal'; - -export const registerWizardSteps = [ +export const getSetupWizardSteps = () => [ { - label: 'payment_or_trial', + label: intl.get('Plans & Payment'), }, { - label: 'initializing', + label: intl.get('Initializing'), }, { - label: 'getting_started', + label: intl.get('Getting started'), }, { - label: 'Congratulations', + label: intl.get('Congratulations'), }, ]; \ No newline at end of file diff --git a/client/src/components/AccountsMultiSelect.js b/client/src/components/AccountsMultiSelect.js index d9e24c7e5..726bd7f18 100644 --- a/client/src/components/AccountsMultiSelect.js +++ b/client/src/components/AccountsMultiSelect.js @@ -2,7 +2,7 @@ import React, { useMemo, useCallback, useState } from 'react'; import { omit } from 'lodash'; import { MenuItem, Button } from '@blueprintjs/core'; import MultiSelect from 'components/MultiSelect'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default function AccountsMultiSelect({ accounts, onAccountSelected }) { const [selectedAccounts, setSelectedAccounts] = useState({}); @@ -56,7 +56,7 @@ export default function AccountsMultiSelect({ accounts, onAccountSelected }) { return ( } + noResults={} />} itemRenderer={accountItem} popoverProps={{ minimal: true }} filterable={true} diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js index 7b173dff2..cddbcbe81 100644 --- a/client/src/components/AccountsSelectList.js +++ b/client/src/components/AccountsSelectList.js @@ -1,7 +1,7 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react'; import { MenuItem, Button } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { filterAccountsByQuery } from './utils'; import { CLASSES } from 'common/classes'; diff --git a/client/src/components/AccountsSuggestField.js b/client/src/components/AccountsSuggestField.js index bdd1742a5..177aecc60 100644 --- a/client/src/components/AccountsSuggestField.js +++ b/client/src/components/AccountsSuggestField.js @@ -1,11 +1,12 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react'; import { MenuItem } from '@blueprintjs/core'; import { Suggest } from '@blueprintjs/select'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { filterAccountsByQuery } from './utils'; /** @@ -15,7 +16,7 @@ export default function AccountsSuggestField({ accounts, initialAccountId, selectedAccountId, - defaultSelectText = 'Select account', + defaultSelectText = intl.formatMessage({ id: 'select_account' }), popoverFill = false, onAccountSelected, @@ -32,7 +33,7 @@ export default function AccountsSuggestField({ filterByRootTypes, filterByParentTypes, filterByTypes, - filterByNormal, + filterByNormal, }); return filteredAccounts; }, [ diff --git a/client/src/components/App.js b/client/src/components/App.js index 9df37df3c..f851f4234 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -1,53 +1,67 @@ import React from 'react'; -import { RawIntlProvider } from 'react-intl'; import { Router, Switch, Route } from 'react-router'; import { createBrowserHistory } from 'history'; import { QueryClientProvider, QueryClient } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import 'style/App.scss'; +import 'moment/locale/ar-ly'; +import 'moment/locale/es-us' +import AppIntlLoader from './AppIntlLoader'; import PrivateRoute from 'components/Guards/PrivateRoute'; -import Authentication from 'components/Authentication'; -import DashboardPrivatePages from 'components/Dashboard/PrivatePages'; import GlobalErrors from 'containers/GlobalErrors/GlobalErrors'; -import intl from 'services/intl'; +import DashboardPrivatePages from 'components/Dashboard/PrivatePages'; +import Authentication from 'components/Authentication'; +// Query client config. +const queryConfig = { + defaultOptions: { + queries: { + refetchOnWindowFocus: true, + staleTime: 30000, + }, + }, +}; + +// Global fetch query. +function GlobalFetchQuery({ + children +}) { + window.localStorage.setItem('lang', 'en'); + return children +} + +/** + * Core application. + */ function App({ locale }) { const history = createBrowserHistory(); - const queryConfig = { - defaultOptions: { - queries: { - refetchOnWindowFocus: true, - staleTime: 30000, - }, - }, - }; + // Query client. const queryClient = new QueryClient(queryConfig); return ( - - -
- - - - - + + + +
+ + + + + + + + - - - - - + +
+
+
- -
- - -
-
+ + ); } diff --git a/client/src/components/AppIntlLoader.js b/client/src/components/AppIntlLoader.js new file mode 100644 index 000000000..fdd533325 --- /dev/null +++ b/client/src/components/AppIntlLoader.js @@ -0,0 +1,95 @@ +import React from 'react'; +import moment from 'moment'; +import { setLocale } from 'yup'; +import intl from 'react-intl-universal'; +import { find } from 'lodash'; +import rtlDetect from 'rtl-detect'; +import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator'; + +const SUPPORTED_LOCALES = [ + { name: 'English', value: 'en' }, + { name: 'العربية', value: 'ar-ly' }, +]; + +/** + * Retrieve the current local. + */ +function getCurrentLocal() { + let currentLocale = intl.determineLocale({ + urlLocaleKey: 'lang', + cookieLocaleKey: 'lang', + localStorageLocaleKey: 'lang', + }); + if (!find(SUPPORTED_LOCALES, { value: currentLocale })) { + currentLocale = 'en'; + } + return currentLocale; +} + +/** + * Loads the localization data of the given locale. + */ +function loadLocales(currentLocale) { + return import(`../lang/${currentLocale}/index.json`); +} + +function loadYupLocales(currentLocale) { + return import(`../lang/${currentLocale}/locale`); +} + +/** + * Modifies the html document direction to RTl if it was rtl-language. + */ +function useDocumentDirectionModifier(locale) { + React.useEffect(() => { + const isRTL = rtlDetect.isRtlLang(locale); + + if (isRTL) { + const htmlDocument = document.querySelector('html'); + htmlDocument.setAttribute('dir', 'rtl'); + htmlDocument.setAttribute('lang', locale); + } + }, []); +} + +/** + * Application Intl loader. + */ +export default function AppIntlLoader({ children }) { + const [isLoading, setIsLoading] = React.useState(true); + const currentLocale = getCurrentLocal(); + + // Modifies the html document direction + useDocumentDirectionModifier(currentLocale); + + React.useEffect(() => { + // Lodas the locales data file. + loadLocales(currentLocale) + .then((results) => { + return intl.init({ + currentLocale, + locales: { + [currentLocale]: results, + }, + }); + }) + .then(() => { + moment.locale(currentLocale); + setIsLoading(false); + }); + }, [currentLocale, setIsLoading]); + + React.useEffect(() => { + loadYupLocales(currentLocale) + .then(({ locale }) => { + setLocale(locale); + }) + .then(() => {}); + }, [currentLocale]); + + return ( + + {children} + + ); +} diff --git a/client/src/components/Authentication.js b/client/src/components/Authentication.js index 9eb01c6c1..8bbb29515 100644 --- a/client/src/components/Authentication.js +++ b/client/src/components/Authentication.js @@ -3,7 +3,7 @@ import { Redirect, Route, Switch, Link, useLocation } from 'react-router-dom'; import BodyClassName from 'react-body-classname'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import authenticationRoutes from 'routes/authentication'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import Icon from 'components/Icon'; import { useIsAuthenticated } from 'hooks/state'; @@ -32,6 +32,7 @@ export default function AuthenticationWrapper({ ...rest }) { > +
diff --git a/client/src/components/CategoriesSelectList.js b/client/src/components/CategoriesSelectList.js index 1345d1f6c..6cd4f83c5 100644 --- a/client/src/components/CategoriesSelectList.js +++ b/client/src/components/CategoriesSelectList.js @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { ListSelect } from 'components'; import { MenuItem } from '@blueprintjs/core'; import { saveInvoke } from 'utils'; diff --git a/client/src/components/ContactSelecetList.js b/client/src/components/ContactSelecetList.js index d19d4943c..02f301fbe 100644 --- a/client/src/components/ContactSelecetList.js +++ b/client/src/components/ContactSelecetList.js @@ -1,5 +1,7 @@ import React, { useCallback, useState, useEffect, useMemo } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; + import { MenuItem, Button } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; import classNames from 'classnames'; @@ -14,7 +16,7 @@ export default function ContactSelecetList({ onContactSelected, popoverFill = false, disabled = false, - buttonProps + buttonProps, }) { const contacts = useMemo( () => @@ -79,7 +81,7 @@ export default function ContactSelecetList({ return ( -

{ text }

+

{text}

-
- { thumbs } -
+
{thumbs}
); -} \ No newline at end of file +} diff --git a/client/src/components/Drawer/DrawerHeaderContent.js b/client/src/components/Drawer/DrawerHeaderContent.js index 648651222..ff506c267 100644 --- a/client/src/components/Drawer/DrawerHeaderContent.js +++ b/client/src/components/Drawer/DrawerHeaderContent.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Classes, Icon, H4, Button } from '@blueprintjs/core'; import withDrawerActions from 'containers/Drawer/withDrawerActions'; diff --git a/client/src/components/DynamicFilter/DynamicFilterCompatatorField.js b/client/src/components/DynamicFilter/DynamicFilterCompatatorField.js index 7559369d6..b1cb3278e 100644 --- a/client/src/components/DynamicFilter/DynamicFilterCompatatorField.js +++ b/client/src/components/DynamicFilter/DynamicFilterCompatatorField.js @@ -1,17 +1,15 @@ import React, { useMemo } from 'react'; import { HTMLSelect, Classes } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { getConditionTypeCompatators } from './DynamicFilterCompatators'; export default function DynamicFilterCompatatorField({ dataType, ...restProps }) { - const { formatMessage } = useIntl(); - const options = useMemo( () => getConditionTypeCompatators(dataType).map(comp => ({ - value: comp.value, label: formatMessage({ id: comp.label_id }), + value: comp.value, label: intl.get(comp.label_id), })), [dataType] ); diff --git a/client/src/components/DynamicFilter/DynamicFilterValueField.js b/client/src/components/DynamicFilter/DynamicFilterValueField.js index 1d56e1bbb..10a9877ad 100644 --- a/client/src/components/DynamicFilter/DynamicFilterValueField.js +++ b/client/src/components/DynamicFilter/DynamicFilterValueField.js @@ -10,7 +10,8 @@ import { connect } from 'react-redux'; import { useQuery } from 'react-query'; import { DateInput } from '@blueprintjs/datetime'; import classNames from 'classnames'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { debounce } from 'lodash'; import moment from 'moment'; @@ -45,7 +46,7 @@ function DynamicFilterValueField({ onChange, inputDebounceWait = 250, }) { - const { formatMessage } = useIntl(); + const [localValue, setLocalValue] = useState(); // Makes `localValue` controlled mode from `value`. @@ -185,7 +186,7 @@ function DynamicFilterValueField({ diff --git a/client/src/components/FilterDropdown.js b/client/src/components/FilterDropdown.js index 04f387972..4bdd172a9 100644 --- a/client/src/components/FilterDropdown.js +++ b/client/src/components/FilterDropdown.js @@ -12,7 +12,8 @@ import { isEqual, last } from 'lodash'; import { usePrevious } from 'react-use'; import Icon from 'components/Icon'; import { checkRequiredProperties, uniqueMultiProps } from 'utils'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { DynamicFilterValueField, DynamicFilterCompatatorField, @@ -41,7 +42,7 @@ export default function FilterDropdown({ initialCondition, initialConditions, }) { - const { formatMessage } = useIntl(); + // Fields key -> metadata table. const fieldsKeyMapped = useMemo(() => @@ -51,10 +52,10 @@ export default function FilterDropdown({ // Conditions options. const conditionalsOptions = useMemo( () => [ - { value: '&&', label: formatMessage({ id: 'and' }) }, - { value: '||', label: formatMessage({ id: 'or' }) }, + { value: '&&', label: intl.get('and') }, + { value: '||', label: intl.get('or') }, ], - [formatMessage], + [], ); // Resources fileds options for fields options. const resourceFieldsOptions = useMemo( @@ -91,7 +92,7 @@ export default function FilterDropdown({ if (values.conditions.length >= 12) { limitToast = Toaster.show( { - message: formatMessage({ id: 'you_reached_conditions_limit' }), + message: intl.get('you_reached_conditions_limit'), intent: Intent.WARNING, }, limitToast, @@ -102,7 +103,7 @@ export default function FilterDropdown({ defaultFilterCondition ]); } - }, [values, setFieldValue, formatMessage, defaultFilterCondition]); + }, [values, setFieldValue, defaultFilterCondition]); // Filtered conditions that filters conditions that don't contain atleast // on required fields or fileds keys that not exists. diff --git a/client/src/components/FinancialSheet.js b/client/src/components/FinancialSheet.js index 0f2373090..49d10231d 100644 --- a/client/src/components/FinancialSheet.js +++ b/client/src/components/FinancialSheet.js @@ -1,7 +1,8 @@ import React, { useMemo, useCallback } from 'react'; import moment from 'moment'; import classnames from 'classnames'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import 'style/pages/FinancialStatements/FinancialSheet.scss'; @@ -23,7 +24,7 @@ export default function FinancialSheet({ fullWidth = false, currentDate = true, }) { - const { formatMessage } = useIntl(); + const format = 'DD MMMM YYYY'; const formattedFromDate = useMemo(() => moment(fromDate).format(format), [ fromDate, @@ -38,10 +39,10 @@ export default function FinancialSheet({ const nameModifer = name ? `financial-sheet--${name}` : ''; const methodsLabels = useMemo( () => ({ - cash: formatMessage({ id: 'cash' }), - accrual: formatMessage({ id: 'accrual' }), + cash: intl.get('cash'), + accrual: intl.get('accrual'), }), - [formatMessage], + [], ); const getBasisLabel = useCallback((b) => methodsLabels[b], [methodsLabels]); const basisLabel = useMemo(() => getBasisLabel(basis), [ diff --git a/client/src/components/FormattedMessage.js b/client/src/components/FormattedMessage.js new file mode 100644 index 000000000..1c181c832 --- /dev/null +++ b/client/src/components/FormattedMessage.js @@ -0,0 +1,9 @@ +import intl from 'react-intl-universal'; + +export function FormattedMessage({ id }) { + return intl.get(id); +} + +export function FormattedHTMLMessage({ ...args }) { + return intl.formatHTMLMessage({ ...args }) +} \ No newline at end of file diff --git a/client/src/components/ItemsListField.js b/client/src/components/ItemsListField.js index 8b23401a4..df92f6d58 100644 --- a/client/src/components/ItemsListField.js +++ b/client/src/components/ItemsListField.js @@ -68,7 +68,7 @@ function ItemsListField({ return ( } + noResults={} />} itemRenderer={itemRenderer} itemPredicate={filterItem} popoverProps={{ minimal: true }} diff --git a/client/src/components/ItemsSuggestField.js b/client/src/components/ItemsSuggestField.js index e1c7178d9..0194b12ea 100644 --- a/client/src/components/ItemsSuggestField.js +++ b/client/src/components/ItemsSuggestField.js @@ -5,7 +5,7 @@ import { CLASSES } from 'common/classes'; import { Suggest } from '@blueprintjs/select'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default function ItemsSuggestField({ items, diff --git a/client/src/components/ListSelect.js b/client/src/components/ListSelect.js index ebbfb5479..1035d184f 100644 --- a/client/src/components/ListSelect.js +++ b/client/src/components/ListSelect.js @@ -1,7 +1,7 @@ import React, { useState, useMemo, useEffect } from 'react'; import { Button, MenuItem } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from './FormattedMessage'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; diff --git a/client/src/components/NumberFormatDropdown/NumberFormatFields.js b/client/src/components/NumberFormatDropdown/NumberFormatFields.js index 169a5a5e9..314a87d01 100644 --- a/client/src/components/NumberFormatDropdown/NumberFormatFields.js +++ b/client/src/components/NumberFormatDropdown/NumberFormatFields.js @@ -3,7 +3,7 @@ import { FastField, ErrorMessage } from 'formik'; import { FormGroup, Checkbox, Switch } from '@blueprintjs/core'; import { CLASSES } from 'common/classes'; import { ListSelect } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { inputIntent } from 'utils'; import { moneyFormat, @@ -73,7 +73,7 @@ export default function NumberFormatFields({}) { label={} helperText={} intent={inputIntent({ error, touched })} - className={classNames(CLASSES.FILL)} + className={classNames('form-group--money-format', CLASSES.FILL)} >
- Page size +
- + {intl.get('showing_current_page_to_total', { + currentPage: state.currentPage, + totalPages: state.totalPages, + total: total, + })}
); diff --git a/client/src/components/PaymentReceiveListField.js b/client/src/components/PaymentReceiveListField.js index 6b6b3a34d..1e7f973ac 100644 --- a/client/src/components/PaymentReceiveListField.js +++ b/client/src/components/PaymentReceiveListField.js @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { MenuItem } from '@blueprintjs/core'; import ListSelect from 'components/ListSelect'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; function PaymentReceiveListField({ invoices, @@ -23,7 +23,7 @@ function PaymentReceiveListField({ return ( } + noResults={} />} itemRenderer={handleInvoiceRenderer} popoverProps={{ minimal: true }} onItemSelect={onInvoiceSelect} diff --git a/client/src/components/Preferences/PreferencesSidebar.js b/client/src/components/Preferences/PreferencesSidebar.js index 9f41daaca..6dbb5a20e 100644 --- a/client/src/components/Preferences/PreferencesSidebar.js +++ b/client/src/components/Preferences/PreferencesSidebar.js @@ -1,6 +1,7 @@ import React from 'react'; import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core'; import { useHistory, useLocation } from 'react-router-dom'; +import { FormattedMessage as T } from 'components'; import preferencesMenu from 'config/preferencesMenu'; import PreferencesSidebarContainer from './PreferencesSidebarContainer'; @@ -32,7 +33,7 @@ export default function PreferencesSidebar() { return (
-

Preferences

+

{}

{items} diff --git a/client/src/components/SalutationList.js b/client/src/components/SalutationList.js index 96b881589..0be3b5169 100644 --- a/client/src/components/SalutationList.js +++ b/client/src/components/SalutationList.js @@ -1,20 +1,27 @@ import React from 'react'; -import { - ListSelect, -} from 'components'; +import intl from 'react-intl-universal'; -export default function SalutationList({ - ...restProps -}) { - const saluations = ['Mr.', 'Mrs.', 'Ms.', 'Miss', 'Dr.']; - const items = saluations.map((saluation) => ({ key: saluation, label: saluation })); +import { ListSelect } from 'components'; + +export default function SalutationList({ ...restProps }) { + const saluations = [ + intl.get('mr'), + intl.get('mrs'), + intl.get('ms'), + intl.get('miss'), + intl.get('dr'), + ]; + const items = saluations.map((saluation) => ({ + key: saluation, + label: saluation, + })); return ( diff --git a/client/src/components/index.js b/client/src/components/index.js index 5b332d816..c8d56dfe9 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -3,6 +3,7 @@ import Money from './Money'; import Icon from './Icon'; import Choose from './Utils/Choose'; import For from './Utils/For'; +import { FormattedMessage, FormattedHTMLMessage } from './FormattedMessage'; import ListSelect from './ListSelect'; import FinancialStatement from './FinancialStatement'; import DynamicFilterValueField from './DynamicFilter/DynamicFilterValueField'; @@ -58,14 +59,19 @@ import MaterialProgressBar from './MaterialProgressBar'; const Hint = FieldHint; +const T = FormattedMessage; + export { If, For, - Money, + Choose, Icon, + FormattedMessage, + FormattedHTMLMessage, + T, + Money, ListSelect, FinancialStatement, - Choose, DynamicFilterValueField, DynamicFilterCompatatorField, MODIFIER, diff --git a/client/src/config/financialReportsMenu.js b/client/src/config/financialReportsMenu.js index 054a60deb..4c3226a4f 100644 --- a/client/src/config/financialReportsMenu.js +++ b/client/src/config/financialReportsMenu.js @@ -1,47 +1,56 @@ +import React from 'react'; +import { FormattedMessage as T } from 'components'; + export const financialReportMenus = [ { - sectionTitle: 'Financial Accounting', + sectionTitle: , reports: [ { - title: 'Balance Sheet Report', - desc: - "Reports a company's assets, liabilities and shareholders' equity at a specific point in time with comparison period(s).", + title: , + desc: ( + + ), link: '/financial-reports/balance-sheet', }, { - title: 'Trial Balance Sheet', - desc: - 'Summarizes the credit and debit balance of each account in your chart of accounts at a specific point in time.', + title: , + desc: ( + + ), link: '/financial-reports/trial-balance-sheet', }, { - title: 'Journal Report', - desc: - 'The debit and credit entries of system transactions, sorted by date.', - link: '/financial-reports/journal-sheet', - }, - { - title: 'Profit/Loss Report', - desc: - 'Reports the revenues, costs and expenses incurred during a specific point in time with comparison period(s).', + title: , + desc: , link: '/financial-reports/profit-loss-sheet', }, { - title: 'General Ledger Report', - desc: - 'Reports every transaction going in and out of your accounts and organized by accounts and date to monitoring activity of accounts.', + title: , + desc: ( + + ), + link: '/financial-reports/cash-flow', + }, + { + title: , + desc: , + link: '/financial-reports/journal-sheet', + }, + { + title: , + desc: , link: '/financial-reports/general-ledger', }, { - title: 'Receivable Aging Summary', - desc: - 'Summarize total unpaid balances of customers invoices with number of days the unpaid invoice is overdue.', + title: , + desc: ( + + ), link: '/financial-reports/receivable-aging-summary', }, { - title: 'Payable Aging Summary', - desc: - 'Summarize total unpaid balances of vendors purchase invoices with the number of days the unpaid invoice is overdue.', + title: , + desc: , link: '/financial-reports/payable-aging-summary', }, ], @@ -50,46 +59,86 @@ export const financialReportMenus = [ export const SalesAndPurchasesReportMenus = [ { - sectionTitle: 'Sales/Purchases Reports', + sectionTitle: , reports: [ { - title: 'Purchases By Items', - desc: - 'Shows the average age of unresolved issues for a project or filter. This helps you see whether your backlog is being kept up to date.', + title: , + desc: ( + + ), link: '/financial-reports/purchases-by-items', }, { - title: 'Sales By Items', - desc: - 'Summarize the business’s sold items quantity, income and average income rate of each item during a specific point in time.', + title: , + desc: ( + + ), link: '/financial-reports/sales-by-items', }, { - title: 'Inventory valuation', - desc: - 'Summarize the business’s purchase items quantity, cost and average cost rate of each item during a specific point in time.', + title: , + desc: ( + + ), link: '/financial-reports/inventory-valuation', }, { - title: 'Customers Balance summary', - desc: 'Summerize the total amount of each customer owes your business.', + title: , + desc: ( + + ), link: '/financial-reports/customers-balance-summary', }, { - title: 'Vendors Balance summary', - desc: 'Summerize the total amount your business owes each vendor.', + title: , + desc: ( + + ), link: '/financial-reports/vendors-balance-summary', }, { - title: 'Customers Transactions', - desc: 'Reports every transaction going in and out of each customer.', + title: , + desc: ( + + ), link: '/financial-reports/transactions-by-customers', }, { - title: 'Vendors Transactions', - desc: 'Reports every transaction going in and out of each vendor/supplier.', + title: , + desc: ( + + ), link: '/financial-reports/transactions-by-vendors', }, + { + title: , + desc: ( + + ), + link: '/financial-reports/inventory-item-details', + }, ], }, ]; diff --git a/client/src/config/footerLinks.js b/client/src/config/footerLinks.js index a361fcb7d..8702f266e 100644 --- a/client/src/config/footerLinks.js +++ b/client/src/config/footerLinks.js @@ -1,29 +1,28 @@ +import intl from 'react-intl-universal'; - - -export default [ +export const getFooterLinks = () => [ { - title: 'Blog', + title: intl.get('blog'), link: '#', }, { - title: 'Support', + title: intl.get('support'), link: '#', }, { - title: 'Status', + title: intl.get('service_status'), link: '#', }, { - title: 'Pricing', + title: intl.get('pricing'), link: '#', }, { - title: 'Solution Partner Program', + title: intl.get('reseller_partner'), link: '#', }, { - title: 'Docs', + title: intl.get('docs'), link: '#', }, { diff --git a/client/src/config/preferencesMenu.js b/client/src/config/preferencesMenu.js index 019555d4a..3e1b981dd 100644 --- a/client/src/config/preferencesMenu.js +++ b/client/src/config/preferencesMenu.js @@ -1,5 +1,5 @@ import React from 'react' -import { FormattedMessage as T} from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default [ { diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 328958ef9..d5ff66556 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default [ { @@ -9,14 +9,14 @@ export default [ matchExact: true, }, { - text: 'Sales & inventory', + text: , label: true, }, { text: , children: [ { - text: , + text: , href: '/items', }, { @@ -67,7 +67,7 @@ export default [ newTabHref: '/bills/new', }, { - text: , + text: , href: '/payment-mades', newTabHref: '/payment-mades/new', }, @@ -100,7 +100,7 @@ export default [ href: '/accounts', }, { - text: , + text: , href: '/manual-journals', }, { diff --git a/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js b/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js index 8fec15341..a061c04ef 100644 --- a/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js +++ b/client/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.js @@ -12,7 +12,7 @@ import { } from '@blueprintjs/core'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useManualJournalsContext } from './ManualJournalsListProvider'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -42,9 +42,7 @@ function ManualJournalActionsBar({ }; // Handle delete button click. - const handleBulkDelete = () => { - - }; + const handleBulkDelete = () => {}; // Handle tab change. const handleTabChange = (customView) => { @@ -77,7 +75,7 @@ function ManualJournalActionsBar({ className={classNames(Classes.MINIMAL, 'button--filter', { 'has-active-filters': false, })} - text="Filter" + text={} icon={} /> diff --git a/client/src/containers/Accounting/JournalsLanding/ManualJournalsEmptyStatus.js b/client/src/containers/Accounting/JournalsLanding/ManualJournalsEmptyStatus.js index d002cb3ab..f3d641f22 100644 --- a/client/src/containers/Accounting/JournalsLanding/ManualJournalsEmptyStatus.js +++ b/client/src/containers/Accounting/JournalsLanding/ManualJournalsEmptyStatus.js @@ -2,17 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function ManualJournalsEmptyStatus() { const history = useHistory(); return ( } description={

- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. +

} action={ @@ -24,11 +24,11 @@ export default function ManualJournalsEmptyStatus() { history.push('/make-journal-entry'); }} > - Make journal + } diff --git a/client/src/containers/Accounting/JournalsLanding/components.js b/client/src/containers/Accounting/JournalsLanding/components.js index 6cb1cdd2f..7a4f70bf7 100644 --- a/client/src/containers/Accounting/JournalsLanding/components.js +++ b/client/src/containers/Accounting/JournalsLanding/components.js @@ -11,11 +11,11 @@ import { MenuDivider, Popover, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import moment from 'moment'; import { Choose, Money, If, Icon } from 'components'; import { safeCallback } from 'utils'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; /** * Amount accessor. @@ -155,24 +155,24 @@ export const ActionsMenu = ({ } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} onClick={safeCallback(onViewDetails, original)} /> } - text={formatMessage({ id: 'publish_journal' })} + text={intl.get('publish_journal')} onClick={safeCallback(onPublish, original)} /> } - text={formatMessage({ id: 'edit_journal' })} + text={intl.get('edit_journal')} onClick={safeCallback(onEdit, original)} /> } intent={Intent.DANGER} onClick={safeCallback(onDelete, original)} diff --git a/client/src/containers/Accounting/JournalsLanding/utils.js b/client/src/containers/Accounting/JournalsLanding/utils.js index aa1540009..dd6f3c21f 100644 --- a/client/src/containers/Accounting/JournalsLanding/utils.js +++ b/client/src/containers/Accounting/JournalsLanding/utils.js @@ -1,5 +1,5 @@ import React from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import moment from 'moment'; import { NoteAccessor, @@ -16,42 +16,42 @@ export const useManualJournalsColumns = () => { () => [ { id: 'date', - Header: formatMessage({ id: 'date' }), + Header: intl.get('date'), accessor: DateAccessor, width: 115, className: 'date', }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: AmountAccessor, className: 'amount', width: 115, }, { id: 'journal_number', - Header: formatMessage({ id: 'journal_no' }), + Header: intl.get('journal_no'), accessor: (row) => `#${row.journal_number}`, className: 'journal_number', width: 100, }, { id: 'journal_type', - Header: formatMessage({ id: 'journal_type' }), + Header: intl.get('journal_type'), accessor: 'journal_type', width: 110, className: 'journal_type', }, { id: 'status', - Header: formatMessage({ id: 'publish' }), + Header: intl.get('publish'), accessor: (row) => StatusAccessor(row), width: 95, className: 'status', }, { id: 'note', - Header: formatMessage({ id: 'note' }), + Header: intl.get('note'), accessor: NoteAccessor, disableSortBy: true, width: 85, @@ -59,7 +59,7 @@ export const useManualJournalsColumns = () => { }, { id: 'created_at', - Header: formatMessage({ id: 'created_at' }), + Header: intl.get('created_at'), accessor: (r) => moment(r.created_at).format('YYYY MMM DD'), width: 125, className: 'created_at', diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntries.schema.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntries.schema.js index cec70cb30..796f505f9 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntries.schema.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntries.schema.js @@ -1,5 +1,5 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ @@ -7,15 +7,15 @@ const Schema = Yup.object().shape({ .required() .min(1) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'journal_number_' })), + .label(intl.get('journal_number_')), journal_type: Yup.string() .required() .min(1) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'journal_type' })), + .label(intl.get('journal_type')), date: Yup.date() .required() - .label(formatMessage({ id: 'date' })), + .label(intl.get('date')), currency_code: Yup.string().max(3), publish: Yup.boolean(), reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING), diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesFooter.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesFooter.js index 8752d8ff6..bd541ec64 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesFooter.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesFooter.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useFormikContext } from 'formik'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesForm.js index 97658ea6f..9b99ebe64 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesForm.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesForm.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { defaultTo, isEmpty, omit } from 'lodash'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -48,7 +48,6 @@ function MakeJournalEntriesForm({ submitPayload, } = useMakeJournalFormContext(); - const { formatMessage } = useIntl(); const history = useHistory(); // New journal number. @@ -92,18 +91,14 @@ function MakeJournalEntriesForm({ // Validate the total credit should be eqials total debit. if (totalCredit !== totalDebit) { AppToaster.show({ - message: formatMessage({ - id: 'should_total_of_credit_and_debit_be_equal', - }), + message: intl.get('should_total_of_credit_and_debit_be_equal'), intent: Intent.DANGER, }); setSubmitting(false); return; } else if (totalCredit === 0 || totalDebit === 0) { AppToaster.show({ - message: formatMessage({ - id: 'amount_cannot_be_zero_or_empty', - }), + message: intl.get('amount_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); setSubmitting(false); @@ -131,12 +126,10 @@ function MakeJournalEntriesForm({ // Handle the request success. const handleSuccess = (errors) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_journal_has_been_created_successfully' - : 'the_journal_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_journal_has_been_created_successfully' + : 'the_journal_has_been_edited_successfully', { number: values.journal_number }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeader.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeader.js index aa037ffb8..3e982cef0 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeader.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeader.js @@ -2,6 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import { useFormikContext } from 'formik'; import { CLASSES } from 'common/classes'; +import { FormattedMessage as T } from 'components'; import MakeJournalEntriesHeaderFields from './MakeJournalEntriesHeaderFields'; import { PageFormBigNumber } from 'components'; import { safeSumBy } from 'utils'; @@ -20,7 +21,7 @@ export default function MakeJournalEntriesHeader() { } amount={total} currencyCode={currency_code} /> diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeaderFields.js b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeaderFields.js index df68373c9..6ec4d25e4 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeaderFields.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalEntriesHeaderFields.js @@ -7,7 +7,7 @@ import { } from '@blueprintjs/core'; import { FastField, ErrorMessage } from 'formik'; import { DateInput } from '@blueprintjs/datetime'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -131,7 +131,9 @@ function MakeJournalEntriesHeader({ }} tooltip={true} tooltipProps={{ - content: 'Setting your auto-generated journal number', + content: ( + + ), position: Position.BOTTOM_LEFT, }} /> diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalFormFloatingActions.js b/client/src/containers/Accounting/MakeJournal/MakeJournalFormFloatingActions.js index 4d4c82fdc..a95429551 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalFormFloatingActions.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalFormFloatingActions.js @@ -11,7 +11,7 @@ import { } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import { Icon, If } from 'components'; import { useHistory } from 'react-router-dom'; diff --git a/client/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.js b/client/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.js index ab47cee7a..fd1315e05 100644 --- a/client/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.js +++ b/client/src/containers/Accounting/MakeJournal/MakeJournalFormFooter.js @@ -3,7 +3,7 @@ import { FastField } from 'formik'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Postbox, ErrorMessage, Row, Col } from 'components'; import Dragzone from 'components/Dragzone'; import { inputIntent } from 'utils'; @@ -11,7 +11,7 @@ import { inputIntent } from 'utils'; export default function MakeJournalFormFooter() { return (
- + } defaultOpen={false}> @@ -34,7 +34,7 @@ export default function MakeJournalFormFooter() { initialFiles={[]} // onDrop={handleDropFiles} // onDeleteFile={handleDeleteFile} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} /> diff --git a/client/src/containers/Accounting/MakeJournal/components.js b/client/src/containers/Accounting/MakeJournal/components.js index 1a0937d55..e7a5dbf86 100644 --- a/client/src/containers/Accounting/MakeJournal/components.js +++ b/client/src/containers/Accounting/MakeJournal/components.js @@ -1,8 +1,8 @@ import React from 'react'; import { Intent, Position, Button, Tooltip } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Icon, Money, Hint } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { AccountsListFieldCell, MoneyFieldCell, @@ -30,21 +30,25 @@ export function ContactHeaderCell() { * Credit header cell. */ export function CreditHeaderCell({ payload: { currencyCode } }) { - return formatMessage({ id: 'credit_currency' }, { currency: currencyCode }); + return intl.get('credit_currency', { currency: currencyCode }); } /** * debit header cell. */ export function DebitHeaderCell({ payload: { currencyCode } }) { - return formatMessage({ id: 'debit_currency' }, { currency: currencyCode }); + return intl.get('debit_currency', { currency: currencyCode }); } /** * Account footer cell. */ function AccountFooterCell({ payload: { currencyCode } }) { - return {`Total ${currencyCode} `}; + return ( + + {intl.get('total_currency', { currency: currencyCode })} + + ); } /** @@ -116,7 +120,7 @@ export const useJournalTableEntriesColumns = () => { sticky: 'left', }, { - Header: formatMessage({ id: 'account' }), + Header: intl.get('account'), id: 'account_id', accessor: 'account_id', Cell: AccountsListFieldCell, @@ -153,7 +157,7 @@ export const useJournalTableEntriesColumns = () => { width: 120, }, { - Header: formatMessage({ id: 'note' }), + Header: intl.get('note'), accessor: 'note', Cell: InputGroupCell, disableSortBy: true, @@ -170,6 +174,6 @@ export const useJournalTableEntriesColumns = () => { width: 45, }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/Accounting/MakeJournal/utils.js b/client/src/containers/Accounting/MakeJournal/utils.js index f8e9ac58b..6a7142f1e 100644 --- a/client/src/containers/Accounting/MakeJournal/utils.js +++ b/client/src/containers/Accounting/MakeJournal/utils.js @@ -10,7 +10,7 @@ import { transformToForm, } from 'utils'; import { AppToaster } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { useFormikContext } from 'formik'; const ERROR = { @@ -118,23 +118,19 @@ export const transformErrors = (resErrors, { setErrors, errors }) => { if ((error = getError(ERROR.RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS))) { toastMessages.push( - formatMessage({ - id: 'should_select_customers_with_entries_have_receivable_account', - }), + intl.get('should_select_customers_with_entries_have_receivable_account'), ); setEntriesErrors(error.indexes, 'contact_id', 'error'); } if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) { if (error.meta.find(meta => meta.contact_type === 'customer')) { toastMessages.push( - formatMessage({ - id: 'receivable_accounts_should_assign_with_customers', - }), + intl.get('receivable_accounts_should_assign_with_customers'), ); } if (error.meta.find(meta => meta.contact_type === 'vendor')) { toastMessages.push( - formatMessage({ id: 'payable_accounts_should_assign_with_vendors' }), + intl.get('payable_accounts_should_assign_with_vendors'), ); } const indexes = error.meta.map((meta => meta.indexes)).flat(); @@ -144,9 +140,7 @@ export const transformErrors = (resErrors, { setErrors, errors }) => { newErrors = setWith( newErrors, 'journal_number', - formatMessage({ - id: 'journal_number_is_already_used', - }), + intl.get('journal_number_is_already_used'), ); } setErrors({ ...newErrors }); diff --git a/client/src/containers/Accounts/AccountsActionsBar.js b/client/src/containers/Accounts/AccountsActionsBar.js index 9920a4f76..2731c823e 100644 --- a/client/src/containers/Accounts/AccountsActionsBar.js +++ b/client/src/containers/Accounts/AccountsActionsBar.js @@ -12,7 +12,8 @@ import { Intent, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { If, DashboardActionViewsList } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -104,7 +105,7 @@ function AccountsActionsBar({ true ? ( ) : ( - + intl.get('count_filters_applied', { count: 0 }) ) } icon={} @@ -159,5 +160,5 @@ export default compose( withAccounts(({ accountsSelectedRows }) => ({ accountsSelectedRows, })), - withAccountsTableActions + withAccountsTableActions, )(AccountsActionsBar); diff --git a/client/src/containers/Accounts/AccountsViewsTabs.js b/client/src/containers/Accounts/AccountsViewsTabs.js index 978b11eaf..c68853536 100644 --- a/client/src/containers/Accounts/AccountsViewsTabs.js +++ b/client/src/containers/Accounts/AccountsViewsTabs.js @@ -1,6 +1,8 @@ import React, { useMemo, useCallback } from 'react'; import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core'; import { pick } from 'lodash'; +import intl from 'react-intl-universal'; + import { DashboardViewsTabs } from 'components'; import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider'; @@ -45,7 +47,7 @@ function AccountsViewsTabs({ } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} onClick={safeCallback(onViewDetails, original)} /> } - text={formatMessage({ id: 'edit_account' })} + text={intl.get('edit_account')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'new_child_account' })} + text={intl.get('new_child_account')} onClick={safeCallback(onNewChild, original)} /> } onClick={safeCallback(onInactivate, original)} /> } onClick={safeCallback(onActivate, original)} /> } intent={Intent.DANGER} onClick={safeCallback(onDelete, original)} @@ -77,7 +76,6 @@ export function ActionsMenu({ * Normal cell. */ export function NormalCell({ cell: { value } }) { - const { formatMessage } = useIntl(); const arrowDirection = value === 'credit' ? 'down' : 'up'; // Can't continue if the value is not `credit` or `debit`. @@ -87,7 +85,7 @@ export function NormalCell({ cell: { value } }) { return ( diff --git a/client/src/containers/Accounts/utils.js b/client/src/containers/Accounts/utils.js index 07768af52..47ed45a5f 100644 --- a/client/src/containers/Accounts/utils.js +++ b/client/src/containers/Accounts/utils.js @@ -1,7 +1,7 @@ import React from 'react'; import { Intent, Tag } from '@blueprintjs/core'; import { If, AppToaster } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { NormalCell, BalanceCell } from './components'; import { isBlank, compose } from 'utils'; @@ -25,17 +25,13 @@ export const accountNameAccessor = (account) => { export const handleDeleteErrors = (errors) => { if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) { AppToaster.show({ - message: formatMessage({ - id: 'you_could_not_delete_predefined_accounts', - }), + message: intl.get('you_could_not_delete_predefined_accounts'), intent: Intent.DANGER, }); } if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) { AppToaster.show({ - message: formatMessage({ - id: 'cannot_delete_account_has_associated_transactions', - }), + message: intl.get('cannot_delete_account_has_associated_transactions'), intent: Intent.DANGER, }); } @@ -56,28 +52,28 @@ export const useAccountsTableColumns = () => { () => [ { id: 'name', - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: 'name', className: 'account_name', width: 200, }, { id: 'code', - Header: formatMessage({ id: 'code' }), + Header: intl.get('code'), accessor: AccountCodeAccessor, className: 'code', width: 80, }, { id: 'type', - Header: formatMessage({ id: 'type' }), + Header: intl.get('type'), accessor: 'account_type_label', className: 'type', width: 140, }, { id: 'normal', - Header: formatMessage({ id: 'normal' }), + Header: intl.get('account_normal'), Cell: NormalCell, accessor: 'account_normal', className: 'normal', @@ -85,13 +81,13 @@ export const useAccountsTableColumns = () => { }, { id: 'currency', - Header: formatMessage({ id: 'currency' }), + Header: intl.get('currency'), accessor: 'currency_code', width: 75, }, { id: 'balance', - Header: formatMessage({ id: 'balance' }), + Header: intl.get('balance'), accessor: 'amount', Cell: BalanceCell, width: 150, diff --git a/client/src/containers/Alerts/AccountActivateAlert.js b/client/src/containers/Alerts/AccountActivateAlert.js index 777af9dfa..591efc428 100644 --- a/client/src/containers/Alerts/AccountActivateAlert.js +++ b/client/src/containers/Alerts/AccountActivateAlert.js @@ -1,7 +1,7 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; -import { AppToaster } from 'components'; +import { AppToaster, FormattedMessage as T } from 'components'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; import withAlertActions from 'containers/Alert/withAlertActions'; @@ -20,7 +20,7 @@ function AccountActivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: activateAccount, isLoading @@ -35,9 +35,7 @@ function AccountActivateAlert({ const handleConfirmAccountActivate = () => { activateAccount(accountId).then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_account_has_been_successfully_activated', - }), + message: intl.get('the_account_has_been_successfully_activated'), intent: Intent.SUCCESS, }); closeAlert('account-activate'); diff --git a/client/src/containers/Alerts/AccountBulkActivateAlert.js b/client/src/containers/Alerts/AccountBulkActivateAlert.js index da2672003..af3f2c513 100644 --- a/client/src/containers/Alerts/AccountBulkActivateAlert.js +++ b/client/src/containers/Alerts/AccountBulkActivateAlert.js @@ -1,8 +1,8 @@ import React, { useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { queryCache } from 'react-query'; -import { AppToaster } from 'components'; +import { FormattedMessage as T, AppToaster } from 'components'; import withAccountsActions from 'containers/Accounts/withAccountsActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; @@ -20,7 +20,6 @@ function AccountBulkActivateAlert({ requestBulkActivateAccounts, }) { - const { formatMessage } = useIntl(); const [isLoading, setLoading] = useState(false); const selectedRowsCount = 0; @@ -35,9 +34,7 @@ function AccountBulkActivateAlert({ requestBulkActivateAccounts(accountsIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_accounts_has_been_successfully_activated', - }), + message: intl.get('the_accounts_has_been_successfully_activated'), intent: Intent.SUCCESS, }); queryCache.invalidateQueries('accounts-table'); @@ -52,9 +49,7 @@ function AccountBulkActivateAlert({ return ( } - confirmButtonText={`${formatMessage({ - id: 'activate', - })} (${selectedRowsCount})`} + confirmButtonText={`${intl.get('activate')} (${selectedRowsCount})`} intent={Intent.WARNING} isOpen={isOpen} onCancel={handleClose} diff --git a/client/src/containers/Alerts/AccountBulkDeleteAlert.js b/client/src/containers/Alerts/AccountBulkDeleteAlert.js index dd18667a1..75c7fc597 100644 --- a/client/src/containers/Alerts/AccountBulkDeleteAlert.js +++ b/client/src/containers/Alerts/AccountBulkDeleteAlert.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { queryCache } from 'react-query'; import { AppToaster } from 'components'; @@ -29,7 +30,7 @@ function AccountBulkDeleteAlert({ // #withAccountsActions requestDeleteBulkAccounts, }) { - const { formatMessage } = useIntl(); + const [isLoading, setLoading] = useState(false); const selectedRowsCount = 0; @@ -43,9 +44,7 @@ function AccountBulkDeleteAlert({ requestDeleteBulkAccounts(accountsIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_accounts_has_been_successfully_deleted', - }), + message: intl.get('the_accounts_has_been_successfully_deleted'), intent: Intent.SUCCESS, }); queryCache.invalidateQueries('accounts-table'); @@ -62,9 +61,7 @@ function AccountBulkDeleteAlert({ return ( } - confirmButtonText={`${formatMessage({ - id: 'delete', - })} (${selectedRowsCount})`} + confirmButtonText={`${intl.get('delete')} (${selectedRowsCount})`} icon="trash" intent={Intent.DANGER} isOpen={isOpen} diff --git a/client/src/containers/Alerts/AccountBulkInactivateAlert.js b/client/src/containers/Alerts/AccountBulkInactivateAlert.js index bb58a7072..8031e4074 100644 --- a/client/src/containers/Alerts/AccountBulkInactivateAlert.js +++ b/client/src/containers/Alerts/AccountBulkInactivateAlert.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { queryCache } from 'react-query'; import { AppToaster } from 'components'; @@ -20,7 +21,7 @@ function AccountBulkInactivateAlert({ closeAlert, }) { - const { formatMessage } = useIntl(); + const [isLoading, setLoading] = useState(false); const selectedRowsCount = 0; @@ -34,9 +35,7 @@ function AccountBulkInactivateAlert({ requestBulkInactiveAccounts(accountsIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_accounts_have_been_successfully_inactivated', - }), + message: intl.get('the_accounts_have_been_successfully_inactivated'), intent: Intent.SUCCESS, }); queryCache.invalidateQueries('accounts-table'); @@ -51,9 +50,7 @@ function AccountBulkInactivateAlert({ return ( } - confirmButtonText={`${formatMessage({ - id: 'inactivate', - })} (${selectedRowsCount})`} + confirmButtonText={`${intl.get('inactivate')} (${selectedRowsCount})`} intent={Intent.WARNING} isOpen={isOpen} onCancel={handleCancel} diff --git a/client/src/containers/Alerts/AccountDeleteAlert.js b/client/src/containers/Alerts/AccountDeleteAlert.js index 15014af68..229de8e82 100644 --- a/client/src/containers/Alerts/AccountDeleteAlert.js +++ b/client/src/containers/Alerts/AccountDeleteAlert.js @@ -1,10 +1,8 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; + import { AppToaster } from 'components'; import { handleDeleteErrors } from 'containers/Accounts/utils'; @@ -28,7 +26,6 @@ function AccountDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); const { isLoading, mutateAsync: deleteAccount } = useDeleteAccount(); // handle cancel delete account alert. @@ -40,9 +37,7 @@ function AccountDeleteAlert({ deleteAccount(accountId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_account_has_been_successfully_deleted', - }), + message: intl.get('the_account_has_been_successfully_deleted'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/AccountInactivateAlert.js b/client/src/containers/Alerts/AccountInactivateAlert.js index e69671850..bea79dd5f 100644 --- a/client/src/containers/Alerts/AccountInactivateAlert.js +++ b/client/src/containers/Alerts/AccountInactivateAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -22,7 +23,7 @@ function AccountInactivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: inactivateAccount, isLoading @@ -35,9 +36,7 @@ function AccountInactivateAlert({ const handleConfirmAccountActive = () => { inactivateAccount(accountId).then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_account_has_been_successfully_inactivated', - }), + message: intl.get('the_account_has_been_successfully_inactivated'), intent: Intent.SUCCESS, }); }).catch(() => { diff --git a/client/src/containers/Alerts/Bills/BillDeleteAlert.js b/client/src/containers/Alerts/Bills/BillDeleteAlert.js index 7ec6085a5..7b41e4762 100644 --- a/client/src/containers/Alerts/Bills/BillDeleteAlert.js +++ b/client/src/containers/Alerts/Bills/BillDeleteAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function BillDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { isLoading, mutateAsync: deleteBillMutate } = useDeleteBill(); // Handle cancel Bill @@ -36,9 +37,7 @@ function BillDeleteAlert({ deleteBillMutate(billId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_bill_has_been_deleted_successfully', - }), + message: intl.get('the_bill_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Bills/BillOpenAlert.js b/client/src/containers/Alerts/Bills/BillOpenAlert.js index 0093b4bb7..6744cd5cc 100644 --- a/client/src/containers/Alerts/Bills/BillOpenAlert.js +++ b/client/src/containers/Alerts/Bills/BillOpenAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -22,7 +23,7 @@ function BillOpenAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { isLoading, mutateAsync: openBillMutate } = useOpenBill(); // Handle cancel open bill alert. @@ -35,9 +36,7 @@ function BillOpenAlert({ openBillMutate(billId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_bill_has_been_opened_successfully', - }), + message: intl.get('the_bill_has_been_opened_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js b/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js index 2f89ee8fb..db736b4a5 100644 --- a/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js +++ b/client/src/containers/Alerts/Currencies/CurrencyDeleteAlert.js @@ -1,10 +1,8 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; + import { AppToaster } from 'components'; import { useDeleteCurrency } from 'hooks/query'; @@ -27,7 +25,7 @@ function CurrencyDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteCurrency, isLoading } = useDeleteCurrency(); // handle cancel delete currency alert. @@ -38,9 +36,7 @@ function CurrencyDeleteAlert({ deleteCurrency(currency_code) .then((response) => { AppToaster.show({ - message: formatMessage({ - id: 'the_currency_has_been_deleted_successfully', - }), + message: intl.get('the_currency_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Customers/CustomerBulkDeleteAlert.js b/client/src/containers/Alerts/Customers/CustomerBulkDeleteAlert.js index 232187816..7b71469ac 100644 --- a/client/src/containers/Alerts/Customers/CustomerBulkDeleteAlert.js +++ b/client/src/containers/Alerts/Customers/CustomerBulkDeleteAlert.js @@ -1,5 +1,6 @@ import React, { useCallback, useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; import { transformErrors } from 'containers/Customers/utils'; @@ -22,7 +23,7 @@ function CustomerBulkDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const [isLoading, setLoading] = useState(false); // handle cancel delete alert. @@ -38,9 +39,7 @@ function CustomerBulkDeleteAlert({ requestDeleteBulkCustomers(customersIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_customers_has_been_deleted_successfully', - }), + message: intl.get('the_customers_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) @@ -51,7 +50,7 @@ function CustomerBulkDeleteAlert({ setLoading(false); closeAlert(name); }); - }, [requestDeleteBulkCustomers, customersIds, formatMessage]); + }, [requestDeleteBulkCustomers, customersIds]); return ( { AppToaster.show({ - message: formatMessage({ - id: 'the_customer_has_been_deleted_successfully', - }), + message: intl.get('the_customer_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) @@ -56,7 +51,7 @@ function CustomerDeleteAlert({ .finally(() => { closeAlert(name); }); - }, [deleteCustomerMutate, customerId, closeAlert, name, formatMessage]); + }, [deleteCustomerMutate, customerId, closeAlert, name]); return ( { AppToaster.show({ - message: formatMessage({ - id: 'the_estimate_has_been_approved_successfully', - }), + message: intl.get('the_estimate_has_been_approved_successfully'), intent: Intent.SUCCESS, }); queryCache.invalidateQueries('estimates-table'); @@ -50,7 +49,7 @@ function EstimateApproveAlert({ .finally(() => { closeAlert(name); }); - }, [estimateId, deliverEstimateMutate, closeAlert, name, formatMessage]); + }, [estimateId, deliverEstimateMutate, closeAlert, name]); return ( { AppToaster.show({ - message: formatMessage({ - id: 'the_estimate_has_been_deleted_successfully', - }), + message: intl.get('the_estimate_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Estimates/EstimateDeliveredAlert.js b/client/src/containers/Alerts/Estimates/EstimateDeliveredAlert.js index d024b8dae..ad9b8b84a 100644 --- a/client/src/containers/Alerts/Estimates/EstimateDeliveredAlert.js +++ b/client/src/containers/Alerts/Estimates/EstimateDeliveredAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { useDeliverEstimate } from 'hooks/query'; @@ -23,7 +24,7 @@ function EstimateDeliveredAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deliverEstimateMutate, isLoading } = useDeliverEstimate(); // Handle cancel delivered estimate alert. @@ -36,9 +37,7 @@ function EstimateDeliveredAlert({ deliverEstimateMutate(estimateId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_estimate_has_been_delivered_successfully', - }), + message: intl.get('the_estimate_has_been_delivered_successfully'), intent: Intent.SUCCESS, }) }) diff --git a/client/src/containers/Alerts/Estimates/EstimateRejectAlert.js b/client/src/containers/Alerts/Estimates/EstimateRejectAlert.js index 2a013ede2..e8822b297 100644 --- a/client/src/containers/Alerts/Estimates/EstimateRejectAlert.js +++ b/client/src/containers/Alerts/Estimates/EstimateRejectAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function EstimateRejectAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: rejectEstimateMutate, isLoading @@ -39,9 +40,7 @@ function EstimateRejectAlert({ rejectEstimateMutate(estimateId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_estimate_has_been_rejected_successfully', - }), + message: intl.get('the_estimate_has_been_rejected_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js b/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js index 73e60e689..eb57b6834 100644 --- a/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js +++ b/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { size } from 'lodash'; import { AppToaster } from 'components'; diff --git a/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js b/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js index e13bd0faa..0abaff14f 100644 --- a/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js +++ b/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -30,7 +27,7 @@ function ExchangeRateDeleteAlert({ mutateAsync: deleteExchangeRate, isLoading, } = useDeleteExchangeRate(); - const { formatMessage } = useIntl(); + // Handle cancel delete exchange rate alert. const handleCancelExchangeRateDelete = () => closeAlert(name); @@ -39,9 +36,7 @@ function ExchangeRateDeleteAlert({ deleteExchangeRate(exchangeRateId) .then((response) => { AppToaster.show({ - message: formatMessage({ - id: 'the_exchange_rates_has_been_deleted_successfully', - }), + message: intl.get('the_exchange_rates_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Expenses/ExpenseBulkDeleteAlert.js b/client/src/containers/Alerts/Expenses/ExpenseBulkDeleteAlert.js index 68693229d..b62a0af34 100644 --- a/client/src/containers/Alerts/Expenses/ExpenseBulkDeleteAlert.js +++ b/client/src/containers/Alerts/Expenses/ExpenseBulkDeleteAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; diff --git a/client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js b/client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js index aee3f817e..b367fec84 100644 --- a/client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js +++ b/client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -20,7 +21,7 @@ function ExpenseDeleteAlert({ isOpen, payload: { expenseId }, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteExpenseMutate, isLoading, @@ -35,8 +36,8 @@ function ExpenseDeleteAlert({ const handleConfirmExpenseDelete = () => { deleteExpenseMutate(expenseId).then(() => { AppToaster.show({ - message: formatMessage( - { id: 'the_expense_has_been_deleted_successfully' }, + message: intl.get( + 'the_expense_has_been_deleted_successfully', { number: expenseId }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Alerts/Expenses/ExpenseDeleteEntriesAlert.js b/client/src/containers/Alerts/Expenses/ExpenseDeleteEntriesAlert.js index 5f73d9382..0af22a14b 100644 --- a/client/src/containers/Alerts/Expenses/ExpenseDeleteEntriesAlert.js +++ b/client/src/containers/Alerts/Expenses/ExpenseDeleteEntriesAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/Expenses/ExpensePublishAlert.js b/client/src/containers/Alerts/Expenses/ExpensePublishAlert.js index fa0d0c73c..d5486266e 100644 --- a/client/src/containers/Alerts/Expenses/ExpensePublishAlert.js +++ b/client/src/containers/Alerts/Expenses/ExpensePublishAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -20,7 +21,7 @@ function ExpensePublishAlert({ payload: { expenseId }, isOpen, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: publishExpenseMutate, isLoading } = usePublishExpense(); const handleCancelPublishExpense = () => { @@ -32,9 +33,7 @@ function ExpensePublishAlert({ publishExpenseMutate(expenseId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_expense_has_been_published', - }), + message: intl.get('the_expense_has_been_published'), intent: Intent.SUCCESS, }); closeAlert(name) diff --git a/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js b/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js index e3a8e9969..241737c22 100644 --- a/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js +++ b/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; import { useDeleteInvoice } from 'hooks/query'; @@ -28,7 +25,7 @@ function InvoiceDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteInvoiceMutate, isLoading } = useDeleteInvoice(); // handle cancel delete invoice alert. @@ -41,9 +38,7 @@ function InvoiceDeleteAlert({ deleteInvoiceMutate(invoiceId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_invoice_has_been_deleted_successfully', - }), + message: intl.get('the_invoice_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Invoices/InvoiceDeliverAlert.js b/client/src/containers/Alerts/Invoices/InvoiceDeliverAlert.js index 5badf9335..267ac87fb 100644 --- a/client/src/containers/Alerts/Invoices/InvoiceDeliverAlert.js +++ b/client/src/containers/Alerts/Invoices/InvoiceDeliverAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { useDeliverInvoice } from 'hooks/query'; @@ -23,7 +24,7 @@ function InvoiceDeliverAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deliverInvoiceMutate, isLoading @@ -39,9 +40,7 @@ function InvoiceDeliverAlert({ deliverInvoiceMutate(invoiceId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_invoice_has_been_delivered_successfully', - }), + message: intl.get('the_invoice_has_been_delivered_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/InventoryAdjustmentDeleteAlert.js b/client/src/containers/Alerts/Items/InventoryAdjustmentDeleteAlert.js index f40e31d36..9959a2dfd 100644 --- a/client/src/containers/Alerts/Items/InventoryAdjustmentDeleteAlert.js +++ b/client/src/containers/Alerts/Items/InventoryAdjustmentDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -28,7 +25,7 @@ function InventoryAdjustmentDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteInventoryAdjMutate, isLoading @@ -44,9 +41,7 @@ function InventoryAdjustmentDeleteAlert({ deleteInventoryAdjMutate(inventoryId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_adjustment_has_been_deleted_successfully', - }), + message: intl.get('the_adjustment_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/ItemActivateAlert.js b/client/src/containers/Alerts/Items/ItemActivateAlert.js index 81f9d21e4..1e85fa791 100644 --- a/client/src/containers/Alerts/Items/ItemActivateAlert.js +++ b/client/src/containers/Alerts/Items/ItemActivateAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -25,7 +26,7 @@ function ItemActivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: activateItem, isLoading } = useActivateItem(); // Handle activate item alert cancel. @@ -38,9 +39,7 @@ function ItemActivateAlert({ activateItem(itemId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_item_has_been_activated_successfully', - }), + message: intl.get('the_item_has_been_activated_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/ItemBulkDeleteAlert.js b/client/src/containers/Alerts/Items/ItemBulkDeleteAlert.js index 6ca13c10d..b19804056 100644 --- a/client/src/containers/Alerts/Items/ItemBulkDeleteAlert.js +++ b/client/src/containers/Alerts/Items/ItemBulkDeleteAlert.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { size } from 'lodash'; import { AppToaster } from 'components'; @@ -26,7 +27,7 @@ function ItemBulkDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const [isLoading, setLoading] = useState(false); // handle cancel item bulk delete alert. @@ -39,9 +40,7 @@ function ItemBulkDeleteAlert({ requestDeleteBulkItems(itemsIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_items_has_been_deleted_successfully', - }), + message: intl.get('the_items_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/ItemCategoryBulkDeleteAlert.js b/client/src/containers/Alerts/Items/ItemCategoryBulkDeleteAlert.js index bbc7ce97f..5505b55fb 100644 --- a/client/src/containers/Alerts/Items/ItemCategoryBulkDeleteAlert.js +++ b/client/src/containers/Alerts/Items/ItemCategoryBulkDeleteAlert.js @@ -1,9 +1,6 @@ import React, { useState } from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { size } from 'lodash'; import { AppToaster } from 'components'; @@ -30,7 +27,7 @@ function ItemCategoryBulkDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const [isLoading, setLoading] = useState(false); // handle cancel bulk delete alert. @@ -44,9 +41,7 @@ function ItemCategoryBulkDeleteAlert({ requestDeleteBulkItemCategories(itemCategoriesIds) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_item_categories_has_been_deleted_successfully', - }), + message: intl.get('the_item_categories_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/ItemCategoryDeleteAlert.js b/client/src/containers/Alerts/Items/ItemCategoryDeleteAlert.js index 13b337c0b..eeeccbc8d 100644 --- a/client/src/containers/Alerts/Items/ItemCategoryDeleteAlert.js +++ b/client/src/containers/Alerts/Items/ItemCategoryDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { useDeleteItemCategory } from 'hooks/query'; @@ -27,7 +24,7 @@ function ItemCategoryDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteItemCategory, isLoading, @@ -43,9 +40,7 @@ function ItemCategoryDeleteAlert({ deleteItemCategory(itemCategoryId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_item_category_has_been_deleted_successfully', - }), + message: intl.get('the_item_category_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Items/ItemDeleteAlert.js b/client/src/containers/Alerts/Items/ItemDeleteAlert.js index e8276af35..7a81baa5e 100644 --- a/client/src/containers/Alerts/Items/ItemDeleteAlert.js +++ b/client/src/containers/Alerts/Items/ItemDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -33,7 +30,7 @@ function ItemDeleteAlert({ setItemsTableState, }) { const { mutateAsync: deleteItem, isLoading } = useDeleteItem(); - const { formatMessage } = useIntl(); + // Handle cancel delete item alert. const handleCancelItemDelete = () => { @@ -45,9 +42,7 @@ function ItemDeleteAlert({ deleteItem(itemId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_item_has_been_deleted_successfully', - }), + message: intl.get('the_item_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); // Reset to page number one. diff --git a/client/src/containers/Alerts/Items/ItemInactivateAlert.js b/client/src/containers/Alerts/Items/ItemInactivateAlert.js index a08e2f70d..79d26e85f 100644 --- a/client/src/containers/Alerts/Items/ItemInactivateAlert.js +++ b/client/src/containers/Alerts/Items/ItemInactivateAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function ItemInactivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: inactivateItem, isLoading } = useInactivateItem(); // Handle cancel inactivate alert. @@ -36,9 +37,7 @@ function ItemInactivateAlert({ inactivateItem(itemId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_item_has_been_inactivated_successfully', - }), + message: intl.get('the_item_has_been_inactivated_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert.js b/client/src/containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert.js index 862a75b99..5d68827cd 100644 --- a/client/src/containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert.js +++ b/client/src/containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/ManualJournals/JournalDeleteAlert.js b/client/src/containers/Alerts/ManualJournals/JournalDeleteAlert.js index 0a1746dac..155506589 100644 --- a/client/src/containers/Alerts/ManualJournals/JournalDeleteAlert.js +++ b/client/src/containers/Alerts/ManualJournals/JournalDeleteAlert.js @@ -1,6 +1,7 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { useDeleteJournal } from 'hooks/query'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function JournalDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteJournalMutate, isLoading } = useDeleteJournal(); // Handle cancel delete manual journal. @@ -36,8 +37,8 @@ function JournalDeleteAlert({ deleteJournalMutate(manualJournalId) .then(() => { AppToaster.show({ - message: formatMessage( - { id: 'the_journal_has_been_deleted_successfully' }, + message: intl.get( + 'the_journal_has_been_deleted_successfully', { number: journalNumber }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Alerts/ManualJournals/JournalDeleteEntriesAlert.js b/client/src/containers/Alerts/ManualJournals/JournalDeleteEntriesAlert.js index 09697f2be..b68615d12 100644 --- a/client/src/containers/Alerts/ManualJournals/JournalDeleteEntriesAlert.js +++ b/client/src/containers/Alerts/ManualJournals/JournalDeleteEntriesAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/ManualJournals/JournalPublishAlert.js b/client/src/containers/Alerts/ManualJournals/JournalPublishAlert.js index 176df458e..7930b4672 100644 --- a/client/src/containers/Alerts/ManualJournals/JournalPublishAlert.js +++ b/client/src/containers/Alerts/ManualJournals/JournalPublishAlert.js @@ -1,6 +1,7 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { usePublishJournal } from 'hooks/query'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function JournalPublishAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: publishJournalMutate, isLoading } = usePublishJournal(); // Handle cancel manual journal alert. @@ -36,9 +37,7 @@ function JournalPublishAlert({ publishJournalMutate(manualJournalId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_manual_journal_has_been_published', - }), + message: intl.get('the_manual_journal_has_been_published'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js b/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js index 9adb0f22d..2fe137383 100644 --- a/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js +++ b/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js b/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js index e9c3a670c..21856e5e6 100644 --- a/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js +++ b/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/PaymentMades/PaymentMadeDeleteAlert.js b/client/src/containers/Alerts/PaymentMades/PaymentMadeDeleteAlert.js index 3d1177057..109cf3f3a 100644 --- a/client/src/containers/Alerts/PaymentMades/PaymentMadeDeleteAlert.js +++ b/client/src/containers/Alerts/PaymentMades/PaymentMadeDeleteAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -23,7 +24,7 @@ function PaymentMadeDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deletePaymentMadeMutate, isLoading, @@ -37,9 +38,7 @@ function PaymentMadeDeleteAlert({ deletePaymentMadeMutate(paymentMadeId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_payment_made_has_been_deleted_successfully', - }), + message: intl.get('the_payment_made_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js b/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js index 9143d98aa..4c634b7d0 100644 --- a/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js +++ b/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; diff --git a/client/src/containers/Alerts/PaymentReceives/PaymentReceiveDeleteAlert.js b/client/src/containers/Alerts/PaymentReceives/PaymentReceiveDeleteAlert.js index 13fddc499..9afc7a453 100644 --- a/client/src/containers/Alerts/PaymentReceives/PaymentReceiveDeleteAlert.js +++ b/client/src/containers/Alerts/PaymentReceives/PaymentReceiveDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { useDeletePaymentReceive } from 'hooks/query'; @@ -27,7 +24,7 @@ function PaymentReceiveDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deletePaymentReceiveMutate, isLoading, @@ -43,9 +40,7 @@ function PaymentReceiveDeleteAlert({ deletePaymentReceiveMutate(paymentReceiveId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_payment_receive_has_been_deleted_successfully', - }), + message: intl.get('the_payment_receive_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Receipts/ReceiptCloseAlert.js b/client/src/containers/Alerts/Receipts/ReceiptCloseAlert.js index 13399e2f3..582033007 100644 --- a/client/src/containers/Alerts/Receipts/ReceiptCloseAlert.js +++ b/client/src/containers/Alerts/Receipts/ReceiptCloseAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { useCloseReceipt } from 'hooks/query'; @@ -23,7 +24,7 @@ function ReceiptCloseAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: closeReceiptMutate, isLoading } = useCloseReceipt(); // handle cancel delete alert. @@ -36,9 +37,7 @@ function ReceiptCloseAlert({ closeReceiptMutate(receiptId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_receipt_has_been_closed_successfully', - }), + message: intl.get('the_receipt_has_been_closed_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Receipts/ReceiptDeleteAlert.js b/client/src/containers/Alerts/Receipts/ReceiptDeleteAlert.js index 37c4cea88..2e1660ac8 100644 --- a/client/src/containers/Alerts/Receipts/ReceiptDeleteAlert.js +++ b/client/src/containers/Alerts/Receipts/ReceiptDeleteAlert.js @@ -1,9 +1,6 @@ import React from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; import { queryCache } from 'react-query'; @@ -28,7 +25,7 @@ function NameDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteReceiptMutate, isLoading @@ -44,9 +41,7 @@ function NameDeleteAlert({ deleteReceiptMutate(receiptId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_receipt_has_been_deleted_successfully', - }), + message: intl.get('the_receipt_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Alerts/Users/UserActivateAlert.js b/client/src/containers/Alerts/Users/UserActivateAlert.js index 28737e586..dabae8ca6 100644 --- a/client/src/containers/Alerts/Users/UserActivateAlert.js +++ b/client/src/containers/Alerts/Users/UserActivateAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Alert, Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; import { useActivateUser } from 'hooks/query'; @@ -23,7 +24,7 @@ function UserActivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: userActivateMutate } = useActivateUser(); @@ -31,9 +32,7 @@ function UserActivateAlert({ userActivateMutate(userId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_user_has_been_activated_successfully', - }), + message: intl.get('the_user_has_been_activated_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Users/UserDeleteAlert.js b/client/src/containers/Alerts/Users/UserDeleteAlert.js index 5c6f8471b..5ca9a60d3 100644 --- a/client/src/containers/Alerts/Users/UserDeleteAlert.js +++ b/client/src/containers/Alerts/Users/UserDeleteAlert.js @@ -1,6 +1,7 @@ import React from 'react'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { useDeleteUser } from 'hooks/query'; import { AppToaster } from 'components'; @@ -24,7 +25,7 @@ function UserDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteUserMutate, isLoading } = useDeleteUser(); const handleCancelUserDelete = () => { @@ -35,9 +36,7 @@ function UserDeleteAlert({ deleteUserMutate(userId) .then((response) => { AppToaster.show({ - message: formatMessage({ - id: 'the_user_has_been_deleted_successfully', - }), + message: intl.get('the_user_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Users/UserInactivateAlert.js b/client/src/containers/Alerts/Users/UserInactivateAlert.js index b6894ae98..49361fce4 100644 --- a/client/src/containers/Alerts/Users/UserInactivateAlert.js +++ b/client/src/containers/Alerts/Users/UserInactivateAlert.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Alert, Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; import { useInactivateUser } from 'hooks/query'; @@ -23,7 +24,7 @@ function UserInactivateAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: userInactivateMutate } = useInactivateUser(); @@ -31,9 +32,7 @@ function UserInactivateAlert({ userInactivateMutate(userId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_user_has_been_inactivated_successfully', - }), + message: intl.get('the_user_has_been_inactivated_successfully'), intent: Intent.SUCCESS, }); closeAlert(name); diff --git a/client/src/containers/Alerts/Vendors/VendorDeleteAlert.js b/client/src/containers/Alerts/Vendors/VendorDeleteAlert.js index cf73a3616..fa8e3098f 100644 --- a/client/src/containers/Alerts/Vendors/VendorDeleteAlert.js +++ b/client/src/containers/Alerts/Vendors/VendorDeleteAlert.js @@ -1,10 +1,8 @@ import React, { useCallback } from 'react'; -import { - FormattedMessage as T, - FormattedHTMLMessage, - useIntl, -} from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { Intent, Alert } from '@blueprintjs/core'; + import { AppToaster } from 'components'; import { transformErrors } from 'containers/Vendors/utils'; import { useDeleteVendor } from 'hooks/query'; @@ -27,7 +25,7 @@ function VendorDeleteAlert({ // #withAlertActions closeAlert, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: deleteVendorMutate, isLoading } = useDeleteVendor(); // Handle cancel delete the vendor. @@ -40,9 +38,7 @@ function VendorDeleteAlert({ deleteVendorMutate(vendorId) .then(() => { AppToaster.show({ - message: formatMessage({ - id: 'the_vendor_has_been_deleted_successfully', - }), + message: intl.get('the_vendor_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) @@ -58,7 +54,7 @@ function VendorDeleteAlert({ .finally(() => { closeAlert(name); }); - }, [deleteVendorMutate, name, closeAlert, vendorId, formatMessage]); + }, [deleteVendorMutate, name, closeAlert, vendorId]); return ( -
+
); -} \ No newline at end of file +} diff --git a/client/src/containers/Authentication/AuthInsider.js b/client/src/containers/Authentication/AuthInsider.js index 4df1d7348..28ecc9d78 100644 --- a/client/src/containers/Authentication/AuthInsider.js +++ b/client/src/containers/Authentication/AuthInsider.js @@ -11,7 +11,6 @@ export default function AuthInsider({ }) { return (
-
{ children }
diff --git a/client/src/containers/Authentication/InviteAcceptForm.js b/client/src/containers/Authentication/InviteAcceptForm.js index 2bc11effc..b3cf19c4b 100644 --- a/client/src/containers/Authentication/InviteAcceptForm.js +++ b/client/src/containers/Authentication/InviteAcceptForm.js @@ -2,7 +2,8 @@ import React from 'react'; import { Intent, Position } from '@blueprintjs/core'; import { Formik } from 'formik'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { isEmpty } from 'lodash'; import { useInviteAcceptContext } from './InviteAcceptProvider'; @@ -12,15 +13,10 @@ import InviteAcceptFormContent from './InviteAcceptFormContent'; export default function InviteAcceptForm() { const history = useHistory(); - const { formatMessage } = useIntl(); // Invite accept context. - const { - inviteAcceptMutate, - inviteMeta, - token, - } = useInviteAcceptContext(); - + const { inviteAcceptMutate, inviteMeta, token } = useInviteAcceptContext(); + // Invite value. const inviteValue = { organization_name: '', @@ -38,8 +34,13 @@ export default function InviteAcceptForm() { inviteAcceptMutate([values, token]) .then((response) => { AppToaster.show({ - message: `Congrats! Your account has been created and invited to - ${inviteValue.organization_name} organization successfully.`, + message: intl.getHTML( + 'congrats_your_account_has_been_created_and_invited', + { + organization_name: inviteValue.organization_name, + }, + ), + intent: Intent.SUCCESS, }); history.push('/auth/login'); @@ -53,7 +54,7 @@ export default function InviteAcceptForm() { }) => { if (errors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) { AppToaster.show({ - message: formatMessage({ id: 'an_unexpected_error_occurred' }), + message: intl.get('an_unexpected_error_occurred'), intent: Intent.DANGER, position: Position.BOTTOM, }); @@ -66,7 +67,7 @@ export default function InviteAcceptForm() { } if (errors.find((e) => e.type === 'INVITE.TOKEN.NOT.FOUND')) { AppToaster.show({ - message: 'An unexpected error occurred', + message: intl.get('an_unexpected_error_occurred'), intent: Intent.DANGER, position: Position.BOTTOM, }); diff --git a/client/src/containers/Authentication/InviteAcceptFormContent.js b/client/src/containers/Authentication/InviteAcceptFormContent.js index b13e15994..a1ab053c7 100644 --- a/client/src/containers/Authentication/InviteAcceptFormContent.js +++ b/client/src/containers/Authentication/InviteAcceptFormContent.js @@ -2,7 +2,8 @@ import React from 'react'; import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core'; import { Form, ErrorMessage, FastField, useFormikContext } from 'formik'; import { Link } from 'react-router-dom'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { inputIntent } from 'utils'; import { Col, Row } from 'components'; import { useInviteAcceptContext } from './InviteAcceptProvider'; @@ -35,7 +36,7 @@ export default function InviteUserFormContent() { {({ form, field, meta: { error, touched } }) => ( } + label={} className={'form-group--first_name'} intent={inputIntent({ error, touched })} helperText={} @@ -53,7 +54,7 @@ export default function InviteUserFormContent() { {({ form, field, meta: { error, touched } }) => ( } + label={} className={'form-group--last_name'} intent={inputIntent({ error, touched })} helperText={} @@ -71,7 +72,7 @@ export default function InviteUserFormContent() { {({ form, field, meta: { error, touched } }) => ( } + label={} className={'form-group--phone_number'} intent={inputIntent({ error, touched })} helperText={} @@ -85,7 +86,9 @@ export default function InviteUserFormContent() { {({ form, field, meta: { error, touched } }) => ( } - labelInfo={} + labelInfo={ + + } className={'form-group--password has-password-revealer'} intent={inputIntent({ error, touched })} helperText={} @@ -102,19 +105,14 @@ export default function InviteUserFormContent() {

- {inviteMeta.email},
+ {inviteMeta.email},

-
- - - {' '} - - - {' '} - - + {intl.getHTML('signing_in_or_creating', { + terms: (msg) => {msg}, + privacy: (msg) => {msg}, + })}

diff --git a/client/src/containers/Authentication/Login.js b/client/src/containers/Authentication/Login.js index c5f23e726..afa1006f8 100644 --- a/client/src/containers/Authentication/Login.js +++ b/client/src/containers/Authentication/Login.js @@ -1,7 +1,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Formik } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import Toaster from 'components/AppToaster'; import AuthInsider from 'containers/Authentication/AuthInsider'; @@ -15,7 +15,7 @@ import { LoginSchema, transformLoginErrorsToToasts } from './utils'; */ export default function Login() { const { mutateAsync: loginMutate } = useAuthLogin(); - + const handleSubmit = (values, { setSubmitting }) => { loginMutate({ crediential: values.crediential, diff --git a/client/src/containers/Authentication/LoginForm.js b/client/src/containers/Authentication/LoginForm.js index 97856d420..737e0c867 100644 --- a/client/src/containers/Authentication/LoginForm.js +++ b/client/src/containers/Authentication/LoginForm.js @@ -7,7 +7,7 @@ import { Checkbox, } from '@blueprintjs/core'; import { Form, ErrorMessage, Field } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { T } from 'components'; import { inputIntent } from 'utils'; import { PasswordRevealer } from './components'; diff --git a/client/src/containers/Authentication/Register.js b/client/src/containers/Authentication/Register.js index 973208228..864f8f9a6 100644 --- a/client/src/containers/Authentication/Register.js +++ b/client/src/containers/Authentication/Register.js @@ -4,8 +4,9 @@ import { Link, useHistory } from 'react-router-dom'; import { Intent, } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T } from 'components'; import AppToaster from 'components/AppToaster'; import AuthInsider from 'containers/Authentication/AuthInsider'; import { useAuthLogin, useAuthRegister } from '../../hooks/query/authentication'; @@ -17,7 +18,6 @@ import { RegisterSchema, transformRegisterErrorsToForm } from './utils'; * Register form. */ export default function RegisterUserForm() { - const { formatMessage } = useIntl(); const history = useHistory(); const { mutateAsync: authLoginMutate } = useAuthLogin(); @@ -48,7 +48,7 @@ export default function RegisterUserForm() { }) .catch(({ response: { data: { errors } } }) => { AppToaster.show({ - message: formatMessage({ id: 'something_wentwrong' }), + message: intl.get('something_wentwrong'), intent: Intent.SUCCESS, }); }); diff --git a/client/src/containers/Authentication/RegisterForm.js b/client/src/containers/Authentication/RegisterForm.js index 22bf5c3bd..f634b2f42 100644 --- a/client/src/containers/Authentication/RegisterForm.js +++ b/client/src/containers/Authentication/RegisterForm.js @@ -7,7 +7,8 @@ import { Spinner, } from '@blueprintjs/core'; import { ErrorMessage, Field, Form } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Link } from 'react-router-dom'; import { Row, Col, If } from 'components'; import { PasswordRevealer } from './components'; @@ -117,15 +118,10 @@ export default function RegisterForm({ isSubmitting }) {

-
- - - {' '} - - - {' '} - - + {intl.getHTML('signing_in_or_creating', { + terms: (msg) => {msg}, + privacy: (msg) => {msg}, + })}

diff --git a/client/src/containers/Authentication/ResetPassword.js b/client/src/containers/Authentication/ResetPassword.js index 8116e60bb..a3ceea2e9 100644 --- a/client/src/containers/Authentication/ResetPassword.js +++ b/client/src/containers/Authentication/ResetPassword.js @@ -5,7 +5,8 @@ import { Position, } from '@blueprintjs/core'; import { Link, useParams, useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { useAuthResetPassword } from 'hooks/query'; @@ -18,7 +19,7 @@ import { ResetPasswordSchema } from './utils'; * Reset password page. */ export default function ResetPassword() { - const { formatMessage } = useIntl(); + const { token } = useParams(); const history = useHistory(); @@ -39,7 +40,7 @@ export default function ResetPassword() { authResetPasswordMutate([token, values]) .then((response) => { AppToaster.show({ - message: formatMessage('password_successfully_updated'), + message: intl.get('password_successfully_updated'), intent: Intent.DANGER, position: Position.BOTTOM, }); @@ -49,7 +50,7 @@ export default function ResetPassword() { .catch(({ response: { data: { errors } } }) => { if (errors.find((e) => e.type === 'TOKEN_INVALID')) { AppToaster.show({ - message: formatMessage({ id: 'an_unexpected_error_occurred' }), + message: intl.get('an_unexpected_error_occurred'), intent: Intent.DANGER, position: Position.BOTTOM, }); diff --git a/client/src/containers/Authentication/ResetPasswordForm.js b/client/src/containers/Authentication/ResetPasswordForm.js index 30a7bb446..0df783c6a 100644 --- a/client/src/containers/Authentication/ResetPasswordForm.js +++ b/client/src/containers/Authentication/ResetPasswordForm.js @@ -1,7 +1,7 @@ import React from 'react'; import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core'; import { Form, ErrorMessage, FastField } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { inputIntent } from 'utils'; /** diff --git a/client/src/containers/Authentication/SendResetPassword.js b/client/src/containers/Authentication/SendResetPassword.js index b467e3cc5..52fe928b2 100644 --- a/client/src/containers/Authentication/SendResetPassword.js +++ b/client/src/containers/Authentication/SendResetPassword.js @@ -1,9 +1,10 @@ import React, { useMemo } from 'react'; import { Formik } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; import { Link, useHistory } from 'react-router-dom'; import { Intent } from '@blueprintjs/core'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T } from 'components'; import { useAuthSendResetPassword } from 'hooks/query'; import Toaster from 'components/AppToaster'; import SendResetPasswordForm from './SendResetPasswordForm'; @@ -16,7 +17,6 @@ import AuthInsider from 'containers/Authentication/AuthInsider'; * Send reset password page. */ export default function SendResetPassword({ requestSendResetPassword }) { - const { formatMessage } = useIntl(); const history = useHistory(); const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword(); @@ -34,9 +34,7 @@ export default function SendResetPassword({ requestSendResetPassword }) { sendResetPasswordMutate({ email: values.crediential }) .then((response) => { AppToaster.show({ - message: formatMessage({ - id: 'check_your_email_for_a_link_to_reset', - }), + message: intl.get('check_your_email_for_a_link_to_reset'), intent: Intent.SUCCESS, }); history.push('/auth/login'); diff --git a/client/src/containers/Authentication/SendResetPasswordForm.js b/client/src/containers/Authentication/SendResetPasswordForm.js index 5107c045d..05ba7032a 100644 --- a/client/src/containers/Authentication/SendResetPasswordForm.js +++ b/client/src/containers/Authentication/SendResetPasswordForm.js @@ -1,26 +1,19 @@ import React from 'react'; -import { - Button, - InputGroup, - Intent, - FormGroup, -} from '@blueprintjs/core'; +import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core'; import { Form, ErrorMessage, FastField } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { inputIntent } from 'utils'; /** * Send reset password form. */ -export default function SendResetPasswordForm({ - isSubmitting -}) { +export default function SendResetPasswordForm({ isSubmitting }) { return (
{({ form, field, meta: { error, touched } }) => ( } intent={inputIntent({ error, touched })} helperText={} className={'form-group--crediential'} diff --git a/client/src/containers/Authentication/components.js b/client/src/containers/Authentication/components.js index 2c3740f8e..f8020db49 100644 --- a/client/src/containers/Authentication/components.js +++ b/client/src/containers/Authentication/components.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import ContentLoader from 'react-content-loader'; import { If, Icon } from 'components'; import { saveInvoke } from 'utils'; diff --git a/client/src/containers/Authentication/utils.js b/client/src/containers/Authentication/utils.js index c0f8f441d..7872c5898 100644 --- a/client/src/containers/Authentication/utils.js +++ b/client/src/containers/Authentication/utils.js @@ -1,6 +1,6 @@ import * as Yup from 'yup'; import { Intent } from '@blueprintjs/core'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const LOGIN_ERRORS = { INVALID_DETAILS: 'INVALID_DETAILS', @@ -13,48 +13,47 @@ const REGISTER_ERRORS = { EMAIL_EXISTS: 'EMAIL.EXISTS', }; - export const LoginSchema = Yup.object().shape({ crediential: Yup.string() .required() .email() - .label(formatMessage({ id: 'email' })), + .label(intl.get('email')), password: Yup.string() .required() .min(4) - .label(formatMessage({ id: 'password' })), + .label(intl.get('password')), }); export const RegisterSchema = Yup.object().shape({ first_name: Yup.string() .required() - .label(formatMessage({ id: 'first_name_' })), + .label(intl.get('first_name_')), last_name: Yup.string() .required() - .label(formatMessage({ id: 'last_name_' })), + .label(intl.get('last_name_')), email: Yup.string() .email() .required() - .label(formatMessage({ id: 'email' })), + .label(intl.get('email')), phone_number: Yup.string() .matches() .required() - .label(formatMessage({ id: 'phone_number_' })), + .label(intl.get('phone_number_')), password: Yup.string() .min(4) .required() - .label(formatMessage({ id: 'password' })), + .label(intl.get('password')), }); export const ResetPasswordSchema = Yup.object().shape({ password: Yup.string() .min(4) .required() - .label(formatMessage({ id: 'password' })), + .label(intl.get('password')), confirm_password: Yup.string() .oneOf([Yup.ref('password'), null]) .required() - .label(formatMessage({ id: 'confirm_password' })), + .label(intl.get('confirm_password')), }); // Validation schema. @@ -62,24 +61,24 @@ export const SendResetPasswordSchema = Yup.object().shape({ crediential: Yup.string() .required() .email() - .label(formatMessage({ id: 'email' })), + .label(intl.get('email')), }); export const InviteAcceptSchema = Yup.object().shape({ first_name: Yup.string() .required() - .label(formatMessage({ id: 'first_name_' })), + .label(intl.get('first_name_')), last_name: Yup.string() .required() - .label(formatMessage({ id: 'last_name_' })), + .label(intl.get('last_name_')), phone_number: Yup.string() .matches() .required() - .label(formatMessage({ id: 'phone_number' })), + .label(intl.get('phone_number')), password: Yup.string() .min(4) .required() - .label(formatMessage({ id: 'password' })), + .label(intl.get('password')), }); export const transformSendResetPassErrorsToToasts = (errors) => { @@ -87,9 +86,7 @@ export const transformSendResetPassErrorsToToasts = (errors) => { if (errors.find((e) => e.type === 'EMAIL.NOT.REGISTERED')) { toastBuilders.push({ - message: formatMessage({ - id: 'we_couldn_t_find_your_account_with_that_email', - }), + message: intl.get('we_couldn_t_find_your_account_with_that_email'), intent: Intent.DANGER, }); } @@ -101,17 +98,13 @@ export const transformLoginErrorsToToasts = (errors) => { if (errors.find((e) => e.type === LOGIN_ERRORS.INVALID_DETAILS)) { toastBuilders.push({ - message: formatMessage({ - id: 'email_and_password_entered_did_not_match', - }), + message: intl.get('email_and_password_entered_did_not_match'), intent: Intent.DANGER, }); } if (errors.find((e) => e.type === LOGIN_ERRORS.USER_INACTIVE)) { toastBuilders.push({ - message: formatMessage({ - id: 'the_user_has_been_suspended_from_admin', - }), + message: intl.get('the_user_has_been_suspended_from_admin'), intent: Intent.DANGER, }); } @@ -119,9 +112,7 @@ export const transformLoginErrorsToToasts = (errors) => { errors.find((e) => e.type === LOGIN_ERRORS.LOGIN_TO_MANY_ATTEMPTS) ) { toastBuilders.push({ - message: formatMessage({ - id: 'your_account_has_been_locked', - }), + message: intl.get('your_account_has_been_locked'), intent: Intent.DANGER, }); } @@ -132,14 +123,10 @@ export const transformRegisterErrorsToForm = (errors) => { const formErrors = {}; if (errors.some((e) => e.type === REGISTER_ERRORS.PHONE_NUMBER_EXISTS)) { - formErrors.phone_number = formatMessage({ - id: 'the_phone_number_already_used_in_another_account', - }); + formErrors.phone_number = intl.get('the_phone_number_already_used_in_another_account'); } if (errors.some((e) => e.type === REGISTER_ERRORS.EMAIL_EXISTS)) { - formErrors.email = formatMessage({ - id: 'the_email_already_used_in_another_account', - }); + formErrors.email = intl.get('the_email_already_used_in_another_account'); } return formErrors; } \ No newline at end of file diff --git a/client/src/containers/Customers/CustomerForm/CustomerAddressTabs.js b/client/src/containers/Customers/CustomerForm/CustomerAddressTabs.js index 6b2da9fce..f48fca831 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerAddressTabs.js +++ b/client/src/containers/Customers/CustomerForm/CustomerAddressTabs.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core'; import { Row, Col } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField, ErrorMessage } from 'formik'; import { inputIntent } from 'utils'; diff --git a/client/src/containers/Customers/CustomerForm/CustomerAttachmentTabs.js b/client/src/containers/Customers/CustomerForm/CustomerAttachmentTabs.js index 1f07e51aa..8709d745e 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerAttachmentTabs.js +++ b/client/src/containers/Customers/CustomerForm/CustomerAttachmentTabs.js @@ -6,6 +6,7 @@ import React, { useCallback, } from 'react'; import Dragzone from 'components/Dragzone'; +import { FormattedMessage as T } from 'components'; function CustomerAttachmentTabs() { return ( @@ -14,7 +15,7 @@ function CustomerAttachmentTabs() { initialFiles={[]} onDrop={null} onDeleteFile={[]} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} />
); diff --git a/client/src/containers/Customers/CustomerForm/CustomerFinancialPanel.js b/client/src/containers/Customers/CustomerForm/CustomerFinancialPanel.js index 504baf907..4193487bb 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerFinancialPanel.js +++ b/client/src/containers/Customers/CustomerForm/CustomerFinancialPanel.js @@ -11,7 +11,7 @@ import { Row, Col, } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useCustomerFormContext } from './CustomerFormProvider'; diff --git a/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js b/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js index d7b1abb0c..2db9b862b 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js +++ b/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { useFormikContext } from 'formik'; diff --git a/client/src/containers/Customers/CustomerForm/CustomerForm.js b/client/src/containers/Customers/CustomerForm/CustomerForm.js index 5d194b604..ea799a2e9 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerForm.js +++ b/client/src/containers/Customers/CustomerForm/CustomerForm.js @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import moment from 'moment'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -75,7 +75,7 @@ function CustomerForm({ // const isNewMode = !customerId; const history = useHistory(); - const { formatMessage } = useIntl(); + /** * Initial values in create and edit mode. @@ -98,11 +98,11 @@ function CustomerForm({ const onSuccess = () => { AppToaster.show({ - message: formatMessage({ - id: isNewMode + message: intl.get( + isNewMode ? 'the_customer_has_been_created_successfully' : 'the_item_customer_has_been_edited_successfully', - }), + ), intent: Intent.SUCCESS, }); setSubmitting(false); diff --git a/client/src/containers/Customers/CustomerForm/CustomerForm.schema.js b/client/src/containers/Customers/CustomerForm/CustomerForm.schema.js index 52b1f6ca7..140abe9c9 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerForm.schema.js +++ b/client/src/containers/Customers/CustomerForm/CustomerForm.schema.js @@ -1,11 +1,11 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; const Schema = Yup.object().shape({ customer_type: Yup.string() .required() .trim() - .label(formatMessage({ id: 'customer_type_' })), + .label(intl.get('customer_type_')), salutation: Yup.string().trim(), first_name: Yup.string().trim(), last_name: Yup.string().trim(), @@ -13,7 +13,7 @@ const Schema = Yup.object().shape({ display_name: Yup.string() .trim() .required() - .label(formatMessage({ id: 'display_name_' })), + .label(intl.get('display_name_')), email: Yup.string().email().nullable(), work_phone: Yup.number(), diff --git a/client/src/containers/Customers/CustomerForm/CustomerFormAfterPrimarySection.js b/client/src/containers/Customers/CustomerForm/CustomerFormAfterPrimarySection.js index 09b2ec694..d409b9d37 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerFormAfterPrimarySection.js +++ b/client/src/containers/Customers/CustomerForm/CustomerFormAfterPrimarySection.js @@ -1,10 +1,12 @@ import React from 'react'; import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core'; import { FastField, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { inputIntent } from 'utils'; export default function CustomerFormAfterPrimarySection({}) { + return (
{/*------------ Customer email -----------*/} @@ -33,7 +35,7 @@ export default function CustomerFormAfterPrimarySection({}) { {({ field, meta: { error, touched } }) => ( )} @@ -43,7 +45,7 @@ export default function CustomerFormAfterPrimarySection({}) { {({ field, meta: { error, touched } }) => ( )} diff --git a/client/src/containers/Customers/CustomerForm/CustomerFormPrimarySection.js b/client/src/containers/Customers/CustomerForm/CustomerFormPrimarySection.js index 6427569f3..a0dcacef3 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerFormPrimarySection.js +++ b/client/src/containers/Customers/CustomerForm/CustomerFormPrimarySection.js @@ -2,7 +2,9 @@ import React from 'react'; import classNames from 'classnames'; import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core'; import { FastField, Field, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; + import { Hint, FieldRequiredHint, @@ -19,7 +21,8 @@ import { useAutofocus } from 'hooks'; */ export default function CustomerFormPrimarySection({}) { const firstNameFieldRef = useAutofocus(); - + + return (
{/**-----------Customer type. -----------*/} @@ -53,7 +56,7 @@ export default function CustomerFormPrimarySection({}) { {({ field, meta: { error, touched } }) => ( (firstNameFieldRef.current = ref)} @@ -65,7 +68,7 @@ export default function CustomerFormPrimarySection({}) { {({ field, meta: { error, touched } }) => ( @@ -29,9 +30,9 @@ export default function RadioCustomer(props) { })} selectedValue={value} > - + diff --git a/client/src/containers/Customers/CustomerForm/CustomersTabs.js b/client/src/containers/Customers/CustomerForm/CustomersTabs.js index 07fdc94c2..4fdac8f77 100644 --- a/client/src/containers/Customers/CustomerForm/CustomersTabs.js +++ b/client/src/containers/Customers/CustomerForm/CustomersTabs.js @@ -1,6 +1,6 @@ import React from 'react'; import { Tabs, Tab } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import CustomerAddressTabs from './CustomerAddressTabs'; import CustomerAttachmentTabs from './CustomerAttachmentTabs'; @@ -8,7 +8,7 @@ import CustomerFinancialPanel from './CustomerFinancialPanel'; import CustomerNotePanel from './CustomerNotePanel'; export default function CustomersTabs() { - const { formatMessage } = useIntl(); + return (
@@ -20,22 +20,22 @@ export default function CustomersTabs() { > } /> } /> } /> } /> diff --git a/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js b/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js index 3e4540c9c..e9c3de802 100644 --- a/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js +++ b/client/src/containers/Customers/CustomersLanding/CustomersActionsBar.js @@ -9,7 +9,8 @@ import { Position, PopoverInteractionKind, } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -41,7 +42,7 @@ function CustomerActionsBar({ const history = useHistory(); // React intl - const { formatMessage } = useIntl(); + // Customers list context. const { customersViews } = useCustomersListContext(); @@ -85,7 +86,7 @@ function CustomerActionsBar({ > } diff --git a/client/src/containers/Customers/CustomersLanding/components.js b/client/src/containers/Customers/CustomersLanding/components.js index 136c35d89..8db27dc6c 100644 --- a/client/src/containers/Customers/CustomersLanding/components.js +++ b/client/src/containers/Customers/CustomersLanding/components.js @@ -11,7 +11,7 @@ import { import { Icon, Money } from 'components'; import { safeCallback } from 'utils'; import { firstLettersArgs } from 'utils'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; /** * Actions menu. @@ -20,28 +20,28 @@ export function ActionsMenu({ row: { original }, payload: { onEdit, onDelete, onDuplicate }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_customer' })} + text={intl.get('edit_customer')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'duplicate' })} + text={intl.get('duplicate')} onClick={safeCallback(onDuplicate, original)} /> } - text={formatMessage({ id: 'delete_customer' })} + text={intl.get('delete_customer')} intent={Intent.DANGER} onClick={safeCallback(onDelete, original)} /> @@ -74,7 +74,7 @@ export function BalanceAccessor(row) { * Retrieve customers table columns. */ export function useCustomersTableColumns() { - const { formatMessage } = useIntl(); + return useMemo( () => [ @@ -89,33 +89,33 @@ export function useCustomersTableColumns() { }, { id: 'display_name', - Header: formatMessage({ id: 'display_name' }), + Header: intl.get('display_name'), accessor: 'display_name', className: 'display_name', width: 150, }, { id: 'company_name', - Header: formatMessage({ id: 'company_name' }), + Header: intl.get('company_name'), accessor: 'company_name', className: 'company_name', width: 150, }, { id: 'work_phone', - Header: formatMessage({ id: 'work_phone' }), + Header: intl.get('work_phone'), accessor: PhoneNumberAccessor, className: 'phone_number', width: 100, }, { id: 'balance', - Header: formatMessage({ id: 'receivable_balance' }), + Header: intl.get('receivable_balance'), accessor: BalanceAccessor, className: 'receivable_balance', width: 100, }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Customers/utils.js b/client/src/containers/Customers/utils.js index 38361f58f..896345482 100644 --- a/client/src/containers/Customers/utils.js +++ b/client/src/containers/Customers/utils.js @@ -1,14 +1,12 @@ import React from 'react'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const transformErrors = (errors) => { if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) { AppToaster.show({ - message: formatMessage({ - id: 'customer_has_sales_invoices', - }), + message: intl.get('customer_has_sales_invoices'), intent: Intent.DANGER, }); } @@ -16,18 +14,13 @@ export const transformErrors = (errors) => { errors.find((error) => error.type === 'SOME.CUSTOMERS.HAVE.SALES_INVOICES') ) { AppToaster.show({ - message: formatMessage({ - id: 'some_customers_have_sales_invoices', - }), + message: intl.get('some_customers_have_sales_invoices'), intent: Intent.DANGER, }); } if (errors.find((error) => error.type === 'CUSTOMER_HAS_TRANSACTIONS')) { AppToaster.show({ - message: formatMessage({ - id: - 'this_customer_cannot_be_deleted_as_it_is_associated_with_transactions', - }), + message: intl.get('this_customer_cannot_be_deleted_as_it_is_associated_with_transactions'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/Dialogs/AccountDialog/AccountDialogForm.js b/client/src/containers/Dialogs/AccountDialog/AccountDialogForm.js index b67415e41..1ac575f2f 100644 --- a/client/src/containers/Dialogs/AccountDialog/AccountDialogForm.js +++ b/client/src/containers/Dialogs/AccountDialog/AccountDialogForm.js @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { omit } from 'lodash'; import { AppToaster } from 'components'; @@ -35,8 +35,6 @@ function AccountFormDialogContent({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); - // Account form context. const { editAccountMutate, @@ -48,7 +46,7 @@ function AccountFormDialogContent({ parentAccountId, accountType, isNewMode, - dialogName + dialogName, } = useAccountDialogContext(); // Form validation schema in create and edit mode. @@ -68,15 +66,13 @@ function AccountFormDialogContent({ closeDialog(dialogName); AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'service_has_been_created_successfully' - : 'service_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'service_has_been_created_successfully' + : 'service_has_been_edited_successfully', { name: toastAccountName, - service: formatMessage({ id: 'account' }), + service: intl.get('account'), }, ), intent: Intent.SUCCESS, @@ -95,7 +91,9 @@ function AccountFormDialogContent({ setSubmitting(false); }; if (accountId) { - editAccountMutate([accountId, form]).then(handleSuccess).catch(handleError); + editAccountMutate([accountId, form]) + .then(handleSuccess) + .catch(handleError); } else { createAccountMutate({ ...form }) .then(handleSuccess) diff --git a/client/src/containers/Dialogs/AccountDialog/AccountDialogFormContent.js b/client/src/containers/Dialogs/AccountDialog/AccountDialogFormContent.js index f91a575ca..f00a20a88 100644 --- a/client/src/containers/Dialogs/AccountDialog/AccountDialogFormContent.js +++ b/client/src/containers/Dialogs/AccountDialog/AccountDialogFormContent.js @@ -1,7 +1,7 @@ import React from 'react'; import { Form, FastField, Field, ErrorMessage, useFormikContext } from 'formik'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Button, Classes, diff --git a/client/src/containers/Dialogs/AccountDialog/AccountForm.schema.js b/client/src/containers/Dialogs/AccountDialog/AccountForm.schema.js index 725842ac6..c26fbbaf3 100644 --- a/client/src/containers/Dialogs/AccountDialog/AccountForm.schema.js +++ b/client/src/containers/Dialogs/AccountDialog/AccountForm.schema.js @@ -1,5 +1,5 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ @@ -7,11 +7,11 @@ const Schema = Yup.object().shape({ .required() .min(3) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'account_name_' })), + .label(intl.get('account_name_')), code: Yup.string().nullable().min(3).max(6), account_type: Yup.string() .required() - .label(formatMessage({ id: 'account_type' })), + .label(intl.get('account_type')), description: Yup.string().min(3).max(DATATYPES_LENGTH.TEXT).nullable().trim(), parent_account_id: Yup.number().nullable(), }); diff --git a/client/src/containers/Dialogs/AccountDialog/index.js b/client/src/containers/Dialogs/AccountDialog/index.js index 834608477..c417ac1de 100644 --- a/client/src/containers/Dialogs/AccountDialog/index.js +++ b/client/src/containers/Dialogs/AccountDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/AccountDialog/utils.js b/client/src/containers/Dialogs/AccountDialog/utils.js index 8dda0baa8..4868c0c74 100644 --- a/client/src/containers/Dialogs/AccountDialog/utils.js +++ b/client/src/containers/Dialogs/AccountDialog/utils.js @@ -1,12 +1,12 @@ -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const transformApiErrors = (errors) => { const fields = {}; if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) { - fields.code = formatMessage({ id: 'account_code_is_not_unique' }); + fields.code = intl.get('account_code_is_not_unique'); } if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) { - fields.name = formatMessage({ id: 'account_name_is_already_used' }); + fields.name = intl.get('account_name_is_already_used'); } return fields; }; diff --git a/client/src/containers/Dialogs/BillNumberDialog/index.js b/client/src/containers/Dialogs/BillNumberDialog/index.js index 835dd660e..428fabd05 100644 --- a/client/src/containers/Dialogs/BillNumberDialog/index.js +++ b/client/src/containers/Dialogs/BillNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateForm.js b/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateForm.js index 87a6c54ed..a1d0f56b1 100644 --- a/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateForm.js +++ b/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateForm.js @@ -1,11 +1,11 @@ import React from 'react'; import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { Formik, Form, Field, ErrorMessage } from 'formik'; import { inputIntent } from 'utils'; import { ListSelect, FieldRequiredHint } from 'components'; import { Button, FormGroup, Intent, Classes } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import { useContactDuplicateFromContext } from './ContactDuplicateProvider'; @@ -25,7 +25,7 @@ function ContactDuplicateForm({ const validationSchema = Yup.object().shape({ contact_type: Yup.string() .required() - .label(formatMessage({ id: 'contact_type_' })), + .label(intl.get('contact_type_')), }); const initialValues = { diff --git a/client/src/containers/Dialogs/ContactDuplicateDialog/index.js b/client/src/containers/Dialogs/ContactDuplicateDialog/index.js index af68c318e..994b972fe 100644 --- a/client/src/containers/Dialogs/ContactDuplicateDialog/index.js +++ b/client/src/containers/Dialogs/ContactDuplicateDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js index f3448171a..5c0eb3a1f 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.js @@ -1,7 +1,8 @@ import React, { useMemo } from 'react'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { AppToaster } from 'components'; import CurrencyFormContent from './CurrencyFormContent'; @@ -27,8 +28,6 @@ function CurrencyForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); - const { createCurrencyMutate, editCurrencyMutate, @@ -62,18 +61,22 @@ function CurrencyForm({ // Handle the request success. const onSuccess = ({ response }) => { AppToaster.show({ - message: formatMessage({ - id: isEditMode + message: intl.get( + isEditMode ? 'the_currency_has_been_edited_successfully' : 'the_currency_has_been_created_successfully', - }), + ), intent: Intent.SUCCESS, }); afterSubmit(response); }; // Handle the response error. - const onError = ({ response: { data: { errors } } }) => { - if (errors.find(e => e.type === 'CURRENCY_CODE_EXISTS')) { + const onError = ({ + response: { + data: { errors }, + }, + }) => { + if (errors.find((e) => e.type === 'CURRENCY_CODE_EXISTS')) { AppToaster.show({ message: 'The given currency code is already exists.', intent: Intent.DANGER, diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js index 556069355..a79f55447 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyForm.schema.js @@ -1,15 +1,15 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ currency_name: Yup.string() .required() - .label(formatMessage({ id: 'currency_name_' })), + .label(intl.get('currency_name_')), currency_code: Yup.string() .max(4) .required() - .label(formatMessage({ id: 'currency_code_' })), + .label(intl.get('currency_code_')), currency_sign: Yup.string().required(), }); diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js index 56ecb2276..b32349edd 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js @@ -1,7 +1,7 @@ import React from 'react'; import { Classes, FormGroup, InputGroup } from '@blueprintjs/core'; import { FastField } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -39,7 +39,7 @@ export default function CurrencyFormFields() { meta: { error, touched }, }) => ( } className={classNames(CLASSES.FILL, 'form-group--type')} > } onItemSelect={(currency) => { setFieldValue('currency_code', currency.currency_code); setFieldValue('currency_name', currency.name); diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFooter.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFooter.js index 5191bbb5b..11649134c 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFooter.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFooter.js @@ -3,7 +3,7 @@ import { useFormikContext } from 'formik'; import { useCurrencyFormContext } from './CurrencyFormProvider'; import { Button, Classes, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withDialogActions from 'containers/Dialog/withDialogActions'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/index.js b/client/src/containers/Dialogs/CurrencyFormDialog/index.js index 6a5fe2b8b..38d075c1f 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/index.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/EstimateNumberDialog/index.js b/client/src/containers/Dialogs/EstimateNumberDialog/index.js index cf2d5b6ba..99f6f6374 100644 --- a/client/src/containers/Dialogs/EstimateNumberDialog/index.js +++ b/client/src/containers/Dialogs/EstimateNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { saveInvoke, compose } from 'utils'; diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js index bec5b7b6b..7992ba0c7 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js @@ -2,7 +2,8 @@ import React, { useMemo } from 'react'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; import moment from 'moment'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { AppToaster } from 'components'; import { CreateExchangeRateFormSchema, @@ -27,7 +28,6 @@ function ExchangeRateForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); const { createExchangeRateMutate, editExchangeRateMutate, @@ -54,9 +54,9 @@ function ExchangeRateForm({ errors.find((error) => error.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED') ) { setErrors({ - exchange_rate: formatMessage({ - id: 'there_is_exchange_rate_in_this_date_with_the_same_currency', - }), + exchange_rate: intl.get( + 'there_is_exchange_rate_in_this_date_with_the_same_currency', + ), }); } }; @@ -71,11 +71,11 @@ function ExchangeRateForm({ }; const onSuccess = ({ response }) => { AppToaster.show({ - message: formatMessage({ - id: !isNewMode + message: intl.get( + !isNewMode ? 'the_exchange_rate_has_been_edited_successfully' : 'the_exchange_rate_has_been_created_successfully', - }), + ), intent: Intent.SUCCESS, }); afterSubmit(response); diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js index 29ced17ca..379ebf363 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js @@ -1,17 +1,17 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ exchange_rate: Yup.number() .required() - .label(formatMessage({ id: 'exchange_rate_' })), + .label(intl.get('exchange_rate_')), currency_code: Yup.string() .max(3) - .required(formatMessage({ id: 'currency_code_' })), + .required(intl.get('currency_code_')), date: Yup.date() .required() - .label(formatMessage({ id: 'date' })), + .label(intl.get('date')), }); export const CreateExchangeRateFormSchema = Schema; diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js index 1b5ae13a9..b326ed441 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js @@ -2,7 +2,7 @@ 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 'react-intl'; +import { FormattedMessage as T } from 'components'; import { momentFormatter, tansformDateValue, diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js index d0790a5f6..1407bfe65 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js @@ -2,7 +2,7 @@ import React from 'react'; import { useFormikContext } from 'formik'; import { Button, Classes, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useExchangeRateFromContext } from './ExchangeRateFormProvider'; import withDialogActions from 'containers/Dialog/withDialogActions'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js index 3c4c8f6ff..0b979a043 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js @@ -1,5 +1,6 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Dialog, DialogSuspense, diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/DecrementAdjustmentFields.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/DecrementAdjustmentFields.js index 557ed8bc7..0ecb6546d 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/DecrementAdjustmentFields.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/DecrementAdjustmentFields.js @@ -3,7 +3,7 @@ import { Field, ErrorMessage, FastField } from 'formik'; import { FormGroup, InputGroup } from '@blueprintjs/core'; import { inputIntent } from 'utils'; import { Row, Col, MoneyInputGroup } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useAutofocus } from 'hooks'; import { decrementQuantity } from './utils'; import { toSafeNumber } from 'utils'; diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/IncrementAdjustmentFields.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/IncrementAdjustmentFields.js index fceb6b243..691ab87f2 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/IncrementAdjustmentFields.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/IncrementAdjustmentFields.js @@ -4,7 +4,7 @@ import { FormGroup, InputGroup } from '@blueprintjs/core'; import { useAutofocus } from 'hooks'; import { Row, Col, MoneyInputGroup } from 'components'; import { inputIntent, toSafeNumber } from 'utils'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { decrementQuantity, incrementQuantity } from './utils'; export default function IncrementAdjustmentFields() { diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFloatingActions.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFloatingActions.js index dde19cf84..fde842fec 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFloatingActions.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFloatingActions.js @@ -1,7 +1,7 @@ import React from 'react'; import { Intent, Button, Classes } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.js index 0b7ed4713..f769e6c40 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.js @@ -3,7 +3,7 @@ import moment from 'moment'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; import { omit, get } from 'lodash'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import 'style/pages/Items/ItemAdjustmentDialog.scss'; @@ -44,7 +44,7 @@ function InventoryAdjustmentForm({ createInventoryAdjMutate, } = useInventoryAdjContext(); - const { formatMessage } = useIntl(); + // Initial form values. const initialValues = { @@ -65,9 +65,7 @@ function InventoryAdjustmentForm({ closeDialog(dialogName); AppToaster.show({ - message: formatMessage({ - id: 'the_make_adjustment_has_been_created_successfully', - }), + message: intl.get('the_make_adjustment_has_been_created_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.schema.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.schema.js index 41a1af3a1..c1881a75e 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.schema.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentForm.schema.js @@ -1,11 +1,11 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ date: Yup.date() .required() - .label(formatMessage({ id: 'date' })), + .label(intl.get('date')), type: Yup.string().required(), adjustment_account_id: Yup.string().required(), item_id: Yup.number().required(), @@ -13,10 +13,10 @@ const Schema = Yup.object().shape({ .required() .min(3) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'reason' })), + .label(intl.get('reason')), quantity_on_hand: Yup.number() .required() - .label(formatMessage({ id: 'qty' })), + .label(intl.get('qty')), quantity: Yup.number().integer().min(1).required(), cost: Yup.number().when(['type'], { is: (type) => type === 'increment', diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFormDialogFields.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFormDialogFields.js index 502b97b63..1ef3e0493 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFormDialogFields.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/InventoryAdjustmentFormDialogFields.js @@ -8,7 +8,8 @@ import { Position, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { DateInput } from '@blueprintjs/datetime'; import { useAutofocus } from 'hooks'; import { ListSelect, FieldRequiredHint, Col, Row } from 'components'; @@ -37,7 +38,7 @@ export default function InventoryAdjustmentFormDialogFields() { const { accounts } = useInventoryAdjContext(); // Intl context. - const { formatMessage } = useIntl(); + return (
@@ -127,9 +128,7 @@ export default function InventoryAdjustmentFormDialogFields() { form.setFieldValue('adjustment_account_id', id) } inputProps={{ - placeholder: formatMessage({ - id: 'select_adjustment_account', - }), + placeholder: intl.get('select_adjustment_account'), }} /> diff --git a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/index.js b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/index.js index 9cbb004f8..e61eada5e 100644 --- a/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/index.js +++ b/client/src/containers/Dialogs/InventoryAdjustmentFormDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'redux'; diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialog.schema.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialog.schema.js index 98c3b7f30..ee0c34702 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialog.schema.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialog.schema.js @@ -1,11 +1,11 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; const Schema = Yup.object().shape({ email: Yup.string() .email() .required() - .label(formatMessage({ id: 'email' })), + .label(intl.get('email')), }); export const InviteUserFormSchema = Schema; \ No newline at end of file diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js index 639ed98c2..4baec0006 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js @@ -2,7 +2,7 @@ import React from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; import { pick, snakeCase } from 'lodash'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { AppToaster } from 'components'; import withDialogActions from 'containers/Dialog/withDialogActions'; @@ -19,7 +19,7 @@ function InviteUserForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); + const { dialogName, @@ -46,9 +46,7 @@ function InviteUserForm({ }; const onSuccess = ({ response }) => { AppToaster.show({ - message: formatMessage({ - id: 'teammate_invited_to_organization_account', - }), + message: intl.get('teammate_invited_to_organization_account'), intent: Intent.SUCCESS, }); afterSubmit(response); diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js index 9bf6400b3..bcc989cea 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormGroup, InputGroup, Intent, Button } from '@blueprintjs/core'; import { FastField, Form, useFormikContext, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; import { inputIntent } from 'utils'; diff --git a/client/src/containers/Dialogs/InviteUserDialog/index.js b/client/src/containers/Dialogs/InviteUserDialog/index.js index e8765e9fd..1f6479fb3 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/index.js +++ b/client/src/containers/Dialogs/InviteUserDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/InviteUserDialog/utils.js b/client/src/containers/Dialogs/InviteUserDialog/utils.js index 205ad922f..b957f731d 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/utils.js +++ b/client/src/containers/Dialogs/InviteUserDialog/utils.js @@ -1,13 +1,13 @@ -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const transformApiErrors = (errors) => { const fields = {}; if (errors.find((error) => error.type === 'EMAIL.ALREADY.INVITED')) { - fields.email = formatMessage({ id: 'email_is_already_used' }); + fields.email = intl.get('email_is_already_used'); } if (errors.find((error) => error.type === 'EMAIL.ALREADY.EXISTS')) { - fields.email = formatMessage({ id: 'email_is_already_used' }); + fields.email = intl.get('email_is_already_used'); } return fields; }; diff --git a/client/src/containers/Dialogs/InvoiceNumberDialog/index.js b/client/src/containers/Dialogs/InvoiceNumberDialog/index.js index 1e90b6808..553f66d2c 100644 --- a/client/src/containers/Dialogs/InvoiceNumberDialog/index.js +++ b/client/src/containers/Dialogs/InvoiceNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose, saveInvoke } from 'utils'; diff --git a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryForm.js b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryForm.js index df4561014..7610f67ea 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryForm.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryForm.js @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Intent } from '@blueprintjs/core'; import { Formik } from 'formik'; @@ -30,7 +30,7 @@ function ItemCategoryForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); + const { isNewMode, itemCategory, @@ -53,7 +53,7 @@ function ItemCategoryForm({ const transformErrors = (errors, { setErrors }) => { if (errors.find((error) => error.type === 'CATEGORY_NAME_EXISTS')) { setErrors({ - name: formatMessage({ id: 'category_name_exists' }), + name: intl.get('category_name_exists'), }); } }; @@ -70,11 +70,11 @@ function ItemCategoryForm({ // Handle the response success. const onSuccess = ({ response }) => { AppToaster.show({ - message: formatMessage({ - id: isNewMode + message: intl.get( + isNewMode ? 'the_item_category_has_been_created_successfully' : 'the_item_category_has_been_edited_successfully', - }), + ), intent: Intent.SUCCESS, }); afterSubmit(response); diff --git a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFields.js b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFields.js index 43b5faca2..66758190c 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFields.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFields.js @@ -1,6 +1,6 @@ import React from 'react'; import { Classes, FormGroup, InputGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { ErrorMessage, FastField } from 'formik'; import { useAutofocus } from 'hooks'; diff --git a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFooter.js b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFooter.js index 93ba496d6..aef7ada83 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFooter.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog/ItemCategoryFormFooter.js @@ -1,6 +1,6 @@ import React from 'react'; import { Classes, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useFormikContext } from 'formik'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Dialogs/ItemCategoryDialog/index.js b/client/src/containers/Dialogs/ItemCategoryDialog/index.js index a728fce64..3f1ed7899 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog/index.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; diff --git a/client/src/containers/Dialogs/ItemCategoryDialog/itemCategoryForm.schema.js b/client/src/containers/Dialogs/ItemCategoryDialog/itemCategoryForm.schema.js index cff9092ea..0fbd7bf0e 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog/itemCategoryForm.schema.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog/itemCategoryForm.schema.js @@ -1,12 +1,12 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ name: Yup.string() .required() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'category_name_' })), + .label(intl.get('category_name_')), description: Yup.string().trim().max(DATATYPES_LENGTH.TEXT).nullable(), }); diff --git a/client/src/containers/Dialogs/JournalNumberDialog/index.js b/client/src/containers/Dialogs/JournalNumberDialog/index.js index ee22c7b6a..a805c063a 100644 --- a/client/src/containers/Dialogs/JournalNumberDialog/index.js +++ b/client/src/containers/Dialogs/JournalNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { saveInvoke, compose } from 'utils'; diff --git a/client/src/containers/Dialogs/PaymentReceiveNumberDialog/index.js b/client/src/containers/Dialogs/PaymentReceiveNumberDialog/index.js index 2019257f7..c8c567da7 100644 --- a/client/src/containers/Dialogs/PaymentReceiveNumberDialog/index.js +++ b/client/src/containers/Dialogs/PaymentReceiveNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { saveInvoke, compose } from 'utils'; diff --git a/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherDialogContent.js b/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherDialogContent.js index cc36aa075..58cf04831 100644 --- a/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherDialogContent.js +++ b/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherDialogContent.js @@ -1,7 +1,7 @@ import React from 'react'; import { Formik } from 'formik'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import * as Yup from 'yup'; import { useHistory } from 'react-router-dom'; import Toaster from 'components/AppToaster'; @@ -26,7 +26,7 @@ function PaymentViaLicenseDialogContent({ // #withDialog closeDialog, }) { - const { formatMessage } = useIntl(); + const history = useHistory(); // Payment via voucher @@ -80,7 +80,7 @@ function PaymentViaLicenseDialogContent({ .required() .min(10) .max(10) - .label(formatMessage({ id: 'license_code' })), + .label(intl.get('license_code')), }); return ( diff --git a/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherForm.js b/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherForm.js index 5ce741460..241661168 100644 --- a/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherForm.js +++ b/client/src/containers/Dialogs/PaymentViaVoucherDialog/PaymentViaVoucherForm.js @@ -1,7 +1,7 @@ import React from 'react'; import { Button, FormGroup, InputGroup, Intent } from '@blueprintjs/core'; import { Form, FastField, ErrorMessage, useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { compose } from 'redux'; import { CLASSES } from 'common/classes'; @@ -10,7 +10,6 @@ import { useAutofocus } from 'hooks'; import withDialogActions from 'containers/Dialog/withDialogActions'; - /** * Payment via license form. */ @@ -31,7 +30,9 @@ function PaymentViaLicenseForm({ return (
-

Please enter your preferred payment method below.

+

+ +

{({ field, meta: { error, touched } }) => ( @@ -72,6 +73,4 @@ function PaymentViaLicenseForm({ ); } -export default compose( - withDialogActions -)(PaymentViaLicenseForm); \ No newline at end of file +export default compose(withDialogActions)(PaymentViaLicenseForm); diff --git a/client/src/containers/Dialogs/PaymentViaVoucherDialog/index.js b/client/src/containers/Dialogs/PaymentViaVoucherDialog/index.js index 7c77c3c45..7e0992deb 100644 --- a/client/src/containers/Dialogs/PaymentViaVoucherDialog/index.js +++ b/client/src/containers/Dialogs/PaymentViaVoucherDialog/index.js @@ -1,6 +1,6 @@ import React, { lazy } from 'react'; import { Dialog, DialogSuspense } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js index 7139ab69c..684c7a02d 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js @@ -1,21 +1,21 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ vendor_id: Yup.string() - .label(formatMessage({ id: 'vendor_name_' })) + .label(intl.get('vendor_name_')) .required(), payment_date: Yup.date() .required() - .label(formatMessage({ id: 'payment_date_' })), + .label(intl.get('payment_date_')), payment_number: Yup.string() .nullable() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'payment_no_' })), + .label(intl.get('payment_no_')), payment_account_id: Yup.number() .required() - .label(formatMessage({ id: 'payment_account_' })), + .label(intl.get('payment_account_')), reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), // statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), entries: Yup.array().of( diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js index ea2413200..cfffd72e7 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js @@ -1,7 +1,7 @@ import React from 'react'; import { Intent, Button, Classes } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js index b2bd5198e..77490260a 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js @@ -1,7 +1,8 @@ import React from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { pick } from 'lodash'; import { AppToaster } from 'components'; @@ -20,7 +21,7 @@ function QuickPaymentMadeForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); + const { bill, dialogName, @@ -51,9 +52,7 @@ function QuickPaymentMadeForm({ // Handle request response success. const onSuccess = () => { AppToaster.show({ - message: formatMessage({ - id: 'the_payment_made_has_been_created_successfully', - }), + message: intl.get('the_payment_made_has_been_created_successfully'), intent: Intent.SUCCESS, }); closeDialog(dialogName); diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js index dc0ea26ea..967fb68e0 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js @@ -1,6 +1,7 @@ import React from 'react'; import { FastField, ErrorMessage } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Classes, FormGroup, @@ -36,7 +37,7 @@ export default function QuickPaymentMadeFormFields() { const { accounts } = useQuickPaymentMadeContext(); // Intl context. - const { formatMessage } = useIntl(); + const paymentMadeFieldRef = useAutofocus(); return ( @@ -161,9 +162,7 @@ export default function QuickPaymentMadeFormFields() { form.setFieldValue('payment_account_id', id) } inputProps={{ - placeholder: formatMessage({ - id: 'select_account', - }), + placeholder: intl.get('select_account'), }} filterByTypes={[ ACCOUNT_TYPE.CASH, diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/index.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/index.js index 12c1bf43a..8436376ca 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/index.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'redux'; diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.js index b3113fd5b..95f14bfe6 100644 --- a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.js +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; // Default initial values of payment made. export const defaultPaymentMade = { @@ -18,15 +18,13 @@ export const transformErrors = (errors, { setFieldError }) => { if (getError('PAYMENT.NUMBER.NOT.UNIQUE')) { setFieldError( 'payment_number', - formatMessage({ id: 'payment_number_is_not_unique' }), + intl.get('payment_number_is_not_unique'), ); } if (getError('INVALID_PAYMENT_AMOUNT')) { setFieldError( 'payment_amount', - formatMessage({ - id: 'the_payment_amount_bigger_than_invoice_due_amount', - }), + intl.get('the_payment_amount_bigger_than_invoice_due_amount'), ); } }; diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js index e3b5ed2f6..feb33d6a9 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js @@ -1,22 +1,22 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ customer_id: Yup.string() - .label(formatMessage({ id: 'customer_name_' })) + .label(intl.get('customer_name_')) .required(), payment_receive_no: Yup.string() .required() .nullable() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'payment_receive_no_' })), + .label(intl.get('payment_receive_no_')), payment_date: Yup.date() .required() - .label(formatMessage({ id: 'payment_date_' })), + .label(intl.get('payment_date_')), deposit_account_id: Yup.number() .required() - .label(formatMessage({ id: 'deposit_account_' })), + .label(intl.get('deposit_account_')), reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), // statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), entries: Yup.array().of( diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js index 84aac1b82..32a82d250 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js @@ -1,7 +1,7 @@ import React from 'react'; import { Intent, Button, Classes } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js index 88d400830..1b2a68b49 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js @@ -1,7 +1,7 @@ import React from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { pick, defaultTo, omit } from 'lodash'; import { AppToaster } from 'components'; @@ -27,7 +27,7 @@ function QuickPaymentReceiveForm({ paymentReceiveNextNumber, preferredDepositAccount }) { - const { formatMessage } = useIntl(); + const { dialogName, invoice, @@ -71,9 +71,7 @@ function QuickPaymentReceiveForm({ // Handle request response success. const onSaved = (response) => { AppToaster.show({ - message: formatMessage({ - id: 'the_payment_receive_transaction_has_been_created', - }), + message: intl.get('the_payment_receive_transaction_has_been_created'), intent: Intent.SUCCESS, }); closeDialog(dialogName); diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js index e16a99241..dd94eb33b 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js @@ -1,6 +1,7 @@ import React from 'react'; import { FastField, ErrorMessage } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { useAutofocus } from 'hooks'; import { Classes, @@ -40,7 +41,7 @@ function QuickPaymentReceiveFormFields({ const { accounts } = useQuickPaymentReceiveContext(); // Intl context. - const { formatMessage } = useIntl(); + const paymentReceiveFieldRef = useAutofocus(); return ( @@ -168,9 +169,7 @@ function QuickPaymentReceiveFormFields({ form.setFieldValue('deposit_account_id', id) } inputProps={{ - placeholder: formatMessage({ - id: 'select_account', - }), + placeholder: intl.get('select_account'), }} filterByTypes={[ ACCOUNT_TYPE.CASH, diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/index.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/index.js index 52909ef46..bc9406397 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/index.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'redux'; diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.js index 68aa9c796..0551cab03 100644 --- a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.js +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const defaultInitialValues = { customer_id: '', @@ -17,21 +17,19 @@ export const transformErrors = (errors, { setFieldError }) => { if (getError('PAYMENT_RECEIVE_NO_EXISTS')) { setFieldError( 'payment_receive_no', - formatMessage({ id: 'payment_number_is_not_unique' }), + intl.get('payment_number_is_not_unique'), ); } if (getError('PAYMENT_RECEIVE_NO_REQUIRED')) { setFieldError( 'payment_receive_no', - formatMessage({ id: 'payment_receive_number_required' }), + intl.get('payment_receive_number_required'), ); } if (getError('INVALID_PAYMENT_AMOUNT')) { setFieldError( 'payment_amount', - formatMessage({ - id: 'the_payment_amount_bigger_than_invoice_due_amount', - }), + intl.get('the_payment_amount_bigger_than_invoice_due_amount'), ); } }; diff --git a/client/src/containers/Dialogs/ReceiptNumberDialog/index.js b/client/src/containers/Dialogs/ReceiptNumberDialog/index.js index 36973de77..432b3cd21 100644 --- a/client/src/containers/Dialogs/ReceiptNumberDialog/index.js +++ b/client/src/containers/Dialogs/ReceiptNumberDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose, saveInvoke } from 'utils'; diff --git a/client/src/containers/Dialogs/UserFormDialog/UserForm.js b/client/src/containers/Dialogs/UserFormDialog/UserForm.js index 79702ed3b..332081ae2 100644 --- a/client/src/containers/Dialogs/UserFormDialog/UserForm.js +++ b/client/src/containers/Dialogs/UserFormDialog/UserForm.js @@ -1,7 +1,7 @@ import React from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { pick, snakeCase } from 'lodash'; import { AppToaster } from 'components'; @@ -20,7 +20,7 @@ function UserForm({ // #withDialogActions closeDialog, }) { - const { formatMessage } = useIntl(); + const { dialogName, @@ -48,9 +48,7 @@ function UserForm({ const onSuccess = ({ response }) => { AppToaster.show({ - message: formatMessage({ - id: 'teammate_invited_to_organization_account', - }), + message: intl.get('teammate_invited_to_organization_account'), intent: Intent.SUCCESS, }); afterSubmit(response); diff --git a/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js b/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js index 99bc2dfca..c7d245827 100644 --- a/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js +++ b/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js @@ -1,21 +1,21 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; const Schema = Yup.object().shape({ email: Yup.string() .email() .required() - .label(formatMessage({ id: 'email' })), + .label(intl.get('email')), first_name: Yup.string() .required() - .label(formatMessage({ id: 'first_name_' })), + .label(intl.get('first_name_')), last_name: Yup.string() .required() - .label(formatMessage({ id: 'last_name_' })), + .label(intl.get('last_name_')), phone_number: Yup.string() .matches() .required() - .label(formatMessage({ id: 'phone_number_' })), + .label(intl.get('phone_number_')), }); export const UserFormSchema = Schema; diff --git a/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js b/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js index 6e3ab48c5..46d168d97 100644 --- a/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js +++ b/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js @@ -7,7 +7,7 @@ import { Button, } from '@blueprintjs/core'; import { FastField, Form, useFormikContext, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; import { inputIntent } from 'utils'; diff --git a/client/src/containers/Dialogs/UserFormDialog/index.js b/client/src/containers/Dialogs/UserFormDialog/index.js index 45881727a..8931b78fe 100644 --- a/client/src/containers/Dialogs/UserFormDialog/index.js +++ b/client/src/containers/Dialogs/UserFormDialog/index.js @@ -1,5 +1,5 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/keyboardShortcutsDialog/KeyboardShortcutsFooter.js b/client/src/containers/Dialogs/keyboardShortcutsDialog/KeyboardShortcutsFooter.js index 03a6845c6..62ef2bf30 100644 --- a/client/src/containers/Dialogs/keyboardShortcutsDialog/KeyboardShortcutsFooter.js +++ b/client/src/containers/Dialogs/keyboardShortcutsDialog/KeyboardShortcutsFooter.js @@ -1,6 +1,6 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import withDialogActions from 'containers/Dialog/withDialogActions'; import { compose } from 'utils'; diff --git a/client/src/containers/Dialogs/keyboardShortcutsDialog/index.js b/client/src/containers/Dialogs/keyboardShortcutsDialog/index.js index 5de6f8df9..c7f0335ef 100644 --- a/client/src/containers/Dialogs/keyboardShortcutsDialog/index.js +++ b/client/src/containers/Dialogs/keyboardShortcutsDialog/index.js @@ -1,5 +1,6 @@ import React, { lazy } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Dialog, DialogSuspense } from 'components'; import withDialogRedux from 'components/DialogReduxConnect'; import { compose } from 'utils'; diff --git a/client/src/containers/Drawers/AccountDrawer/AccountDrawerActionBar.js b/client/src/containers/Drawers/AccountDrawer/AccountDrawerActionBar.js index 50b2f9b1e..c8e6b4e61 100644 --- a/client/src/containers/Drawers/AccountDrawer/AccountDrawerActionBar.js +++ b/client/src/containers/Drawers/AccountDrawer/AccountDrawerActionBar.js @@ -1,7 +1,7 @@ import React from 'react'; import Icon from 'components/Icon'; import { Button, Classes, NavbarGroup, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js b/client/src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js index e24b84246..5741eb83a 100644 --- a/client/src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js +++ b/client/src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Icon, Money } from 'components'; /** @@ -18,7 +18,9 @@ export default function AccountDrawerHeader({ return (
- Closing Balance + + +

{}

diff --git a/client/src/containers/Drawers/AccountDrawer/AccountDrawerTable.js b/client/src/containers/Drawers/AccountDrawer/AccountDrawerTable.js index b97c1d7d4..c8be64528 100644 --- a/client/src/containers/Drawers/AccountDrawer/AccountDrawerTable.js +++ b/client/src/containers/Drawers/AccountDrawer/AccountDrawerTable.js @@ -3,7 +3,7 @@ import moment from 'moment'; import { Link } from 'react-router-dom'; import { useAccountDrawerContext } from './AccountDrawerProvider'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DataTable, Money } from 'components'; import { isBlank, compose } from 'utils'; import withDrawerActions from 'containers/Drawer/withDrawerActions'; @@ -11,29 +11,27 @@ import withDrawerActions from 'containers/Drawer/withDrawerActions'; /** * account drawer table. */ -function AccountDrawerTable({ - closeDrawer -}) { +function AccountDrawerTable({ closeDrawer }) { const { account: { currency_code }, accounts, - drawerName + drawerName, } = useAccountDrawerContext(); const columns = React.useMemo( () => [ { - Header: formatMessage({ id: 'transaction_date' }), + Header: intl.get('transaction_date'), accessor: ({ date }) => moment(date).format('YYYY MMM DD'), width: 110, }, { - Header: formatMessage({ id: 'transaction_type' }), + Header: intl.get('transaction_type'), accessor: 'reference_type_formatted', width: 100, }, { - Header: formatMessage({ id: 'credit' }), + Header: intl.get('credit'), accessor: ({ credit }) => !isBlank(credit) && credit !== 0 ? ( @@ -41,7 +39,7 @@ function AccountDrawerTable({ width: 80, }, { - Header: formatMessage({ id: 'debit' }), + Header: intl.get('debit'), accessor: ({ debit }) => !isBlank(debit) && debit !== 0 ? ( @@ -49,7 +47,7 @@ function AccountDrawerTable({ width: 80, }, { - Header: formatMessage({ id: 'running_balance' }), + Header: intl.get('running_balance'), accessor: ({ running_balance }) => ( ), @@ -69,14 +67,15 @@ function AccountDrawerTable({
); } -export default compose( - withDrawerActions -)(AccountDrawerTable); \ No newline at end of file +export default compose(withDrawerActions)(AccountDrawerTable); diff --git a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerActionBar.js b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerActionBar.js index d7b899899..bd2b3329f 100644 --- a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerActionBar.js +++ b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerActionBar.js @@ -2,7 +2,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import Icon from 'components/Icon'; import { Button, Classes, NavbarGroup, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { safeCallback } from 'utils'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; diff --git a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js index d042363f4..a74d32ca7 100644 --- a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js +++ b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js @@ -1,5 +1,6 @@ import React from 'react'; import { If, Money } from 'components'; +import { FormattedMessage as T } from 'components'; export default function ExpenseDrawerFooter({ expense: { total_amount, currency_code }, @@ -8,11 +9,11 @@ export default function ExpenseDrawerFooter({
- Sub Total +

{}

- Total +

{}

diff --git a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js index 3161dfa56..9500c3f04 100644 --- a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js +++ b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js @@ -1,7 +1,7 @@ import React from 'react'; import moment from 'moment'; import { If, Money } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; /** * Expense drawer content. diff --git a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js index f7c4e9652..ae15afac0 100644 --- a/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js +++ b/client/src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js @@ -1,6 +1,6 @@ import React from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DataTable, Money } from 'components'; /** @@ -12,19 +12,19 @@ export default function ExpenseDrawerTable({ const columns = React.useMemo( () => [ { - Header: formatMessage({ id: 'Expense account' }), + Header: intl.get('expense_account'), accessor: 'expense_account.name', width: 110, }, { - Header: formatMessage({ id: 'Amount' }), + Header: intl.get('amount'), accessor: ({ amount }) => ( ), width: 100, }, { - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', width: 110, }, diff --git a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerActionBar.js b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerActionBar.js index 8c1f1ba46..73d22029f 100644 --- a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerActionBar.js +++ b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerActionBar.js @@ -2,7 +2,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import Icon from 'components/Icon'; import { Button, Classes, NavbarGroup, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { safeCallback } from 'utils'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; diff --git a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerFooter.js b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerFooter.js index 5c1267c82..29451caac 100644 --- a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerFooter.js +++ b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerFooter.js @@ -1,4 +1,5 @@ import React from 'react'; +import { FormattedMessage as T } from 'components'; export default function ManualJournalDrawerFooter({ manualJournal: { amount_formatted }, @@ -7,11 +8,15 @@ export default function ManualJournalDrawerFooter({
- Sub Total + + +

{amount_formatted}

- Total + + +

{amount_formatted}

diff --git a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerHeader.js b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerHeader.js index 275d409c4..28daba3e0 100644 --- a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerHeader.js +++ b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerHeader.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; /** * Manual journal details header. diff --git a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerTable.js b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerTable.js index 88d4d08f6..e0d59a46f 100644 --- a/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerTable.js +++ b/client/src/containers/Drawers/ManualJournalDrawer/ManualJournalDrawerTable.js @@ -1,7 +1,7 @@ import React from 'react'; import { Classes, Tooltip, Position } from '@blueprintjs/core'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DataTable, Money, If, Icon } from 'components'; import { isBlank } from 'utils'; @@ -32,17 +32,17 @@ export default function ManualJournalDrawerTable({ const columns = React.useMemo( () => [ { - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: 'account.name', width: 130, }, { - Header: formatMessage({ id: 'contact' }), + Header: intl.get('contact'), accessor: 'contact.display_name', width: 130, }, { - Header: formatMessage({ id: 'credit' }), + Header: intl.get('credit'), accessor: ({ credit }) => !isBlank(credit) && credit !== 0 ? ( @@ -50,7 +50,7 @@ export default function ManualJournalDrawerTable({ width: 80, }, { - Header: formatMessage({ id: 'debit' }), + Header: intl.get('debit'), accessor: ({ debit }) => !isBlank(debit) && debit !== 0 ? ( @@ -58,7 +58,7 @@ export default function ManualJournalDrawerTable({ width: 80, }, { - Header: formatMessage({ id: 'note' }), + Header: intl.get('note'), accessor: NoteAccessor, width: 80, }, diff --git a/client/src/containers/Drawers/PaperTemplate/PaperTemplate.js b/client/src/containers/Drawers/PaperTemplate/PaperTemplate.js index cb368f254..9c12fc7fd 100644 --- a/client/src/containers/Drawers/PaperTemplate/PaperTemplate.js +++ b/client/src/containers/Drawers/PaperTemplate/PaperTemplate.js @@ -3,18 +3,19 @@ import PaperTemplateHeader from './PaperTemplateHeader'; import PaperTemplateTable from './PaperTemplateTable'; import PaperTemplateFooter from './PaperTemplateFooter'; import { updateItemsEntriesTotal } from 'containers/Entries/utils'; +import intl from 'react-intl-universal'; import 'style/components/Drawer/DrawerTemplate.scss'; function PaperTemplate({ labels: propLabels, paperData, entries }) { const labels = { - name: 'Estimate', - billedTo: 'Billed to', - date: 'Estimate date', - refNo: 'Estimate No.', - billedFrom: 'Billed from', - amount: 'Estimate amount', - dueDate: 'Due date', + name: intl.get('estimate_'), + billedTo: intl.get('billed_to'), + date: intl.get('estimate_date'), + refNo: intl.get('estimate_no'), + billedFrom: intl.get('billed_from'), + amount: intl.get('estimate_amount'), + dueDate: intl.get('due_date_'), ...propLabels, }; diff --git a/client/src/containers/Drawers/PaperTemplate/PaperTemplateTable.js b/client/src/containers/Drawers/PaperTemplate/PaperTemplateTable.js index 6c5695924..db26490d5 100644 --- a/client/src/containers/Drawers/PaperTemplate/PaperTemplateTable.js +++ b/client/src/containers/Drawers/PaperTemplate/PaperTemplateTable.js @@ -1,31 +1,31 @@ import React, { useMemo } from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DataTable, Money } from 'components'; export default function DrawerTemplateTable({ tableData, currencyCode }) { const columns = useMemo( () => [ { - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', disableSortBy: true, width: 150, }, { - Header: formatMessage({ id: 'rate' }), + Header: intl.get('rate'), accessor: 'rate', accessor: ({ rate }) => , disableSortBy: true, width: 80, }, { - Header: formatMessage({ id: 'Qty' }), + Header: intl.get('qty'), accessor: 'quantity', disableSortBy: true, - width: 50, + width: 80, }, { - Header: formatMessage({ id: 'Total' }), + Header: intl.get('total'), accessor: ({ total }) => ( ), diff --git a/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplate.js b/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplate.js index fef1b05b5..b0fbb1ebb 100644 --- a/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplate.js +++ b/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplate.js @@ -1,6 +1,7 @@ import React from 'react'; import PaymentPaperTemplateHeader from './PaymentPaperTemplateHeader'; import PaymentPaperTemplateTable from './PaymentPaperTemplateTable'; +import intl from 'react-intl-universal'; import 'style/components/Drawer/DrawerTemplate.scss'; @@ -9,14 +10,14 @@ export default function PaymentPaperTemplate({ paperData, }) { const labels = { - name: 'Payment receive', - billedTo: 'Billed to', - date: 'Payment date', - refNo: 'Payment No.', - billedFrom: 'Billed from', - referenceNo: 'Reference No', - amount: 'Amount received', - dueDate: 'Due date', + name: intl.get('payment_receive'), + billedTo: intl.get('billed_to'), + date: intl.get('payment_date_'), + refNo: intl.get('payment_no'), + billedFrom: intl.get('billed_from'), + referenceNo: intl.get('reference_no'), + amount: intl.get('amount_received'), + dueDate: intl.get('due_date_'), ...propLabels, }; const defaultValues = { diff --git a/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplateTable.js b/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplateTable.js index bb45eb1c6..52e53b606 100644 --- a/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplateTable.js +++ b/client/src/containers/Drawers/PaymentPaperTemplate/PaymentPaperTemplateTable.js @@ -1,31 +1,31 @@ import React from 'react'; import moment from 'moment'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DataTable, Money } from 'components'; export default function PaymentPaperTemplateTable({ tableData, currencyCode }) { const columns = React.useMemo( () => [ { - Header: formatMessage({ id: 'invoice_number' }), + Header: intl.get('invoice_number'), accessor: 'invoice.invoice_no', disableSortBy: true, }, { - Header: formatMessage({ id: 'invoice_date' }), + Header: intl.get('invoice_date'), accessor: ({ invoice_date }) => moment(invoice_date).format('YYYY MMM DD'), disableSortBy: true, }, { - Header: formatMessage({ id: 'invoice_amount' }), + Header: intl.get('invoice_amount'), accessor: ({ invoice }) => ( ), disableSortBy: true, }, { - Header: formatMessage({ id: 'payment_amount' }), + Header: intl.get('payment_amount'), accessor: ({ payment_amount }) => ( ), diff --git a/client/src/containers/Entries/components.js b/client/src/containers/Entries/components.js index 70118e59b..44cb44d11 100644 --- a/client/src/containers/Entries/components.js +++ b/client/src/containers/Entries/components.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Tooltip, Button, Intent, Position } from '@blueprintjs/core'; import { Hint, Icon } from 'components'; import { formattedAmount, safeSumBy } from 'utils'; @@ -27,7 +28,7 @@ export function ItemHeaderCell() { * Item column footer cell. */ export function ItemFooterCell() { - return Total; + return ; } /** @@ -89,7 +90,7 @@ export function IndexTableCell({ row: { index } }) { * Retrieve editable items entries columns. */ export function useEditableItemsEntriesColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ @@ -113,7 +114,7 @@ export function useEditableItemsEntriesColumns() { className: 'item', }, { - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', Cell: InputGroupCell, disableSortBy: true, @@ -121,7 +122,7 @@ export function useEditableItemsEntriesColumns() { width: 120, }, { - Header: formatMessage({ id: 'quantity' }), + Header: intl.get('quantity'), accessor: 'quantity', Cell: NumericInputCell, Footer: QuantityTotalFooterCell, @@ -130,7 +131,7 @@ export function useEditableItemsEntriesColumns() { className: 'quantity', }, { - Header: formatMessage({ id: 'rate' }), + Header: intl.get('rate'), accessor: 'rate', Cell: MoneyFieldCell, disableSortBy: true, @@ -138,7 +139,7 @@ export function useEditableItemsEntriesColumns() { className: 'rate', }, { - Header: formatMessage({ id: 'discount' }), + Header: intl.get('discount'), accessor: 'discount', Cell: PercentFieldCell, disableSortBy: true, @@ -146,7 +147,7 @@ export function useEditableItemsEntriesColumns() { className: 'discount', }, { - Header: formatMessage({ id: 'total' }), + Header: intl.get('total'), Footer: TotalFooterCell, accessor: 'total', Cell: TotalCell, @@ -164,6 +165,6 @@ export function useEditableItemsEntriesColumns() { width: 45, }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/ExchangeRates/ExchangeRateActionsBar.js b/client/src/containers/ExchangeRates/ExchangeRateActionsBar.js index 49e20c7d4..db6230450 100644 --- a/client/src/containers/ExchangeRates/ExchangeRateActionsBar.js +++ b/client/src/containers/ExchangeRates/ExchangeRateActionsBar.js @@ -10,7 +10,8 @@ import { PopoverInteractionKind, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { connect } from 'react-redux'; import { If } from 'components'; @@ -43,7 +44,7 @@ function ExchangeRateActionsBar({ onBulkDelete, }) { const [filterCount, setFilterCount] = useState(0); - const { formatMessage } = useIntl(); + const onClickNewExchangeRate = () => { openDialog('exchangeRate-form', {}); @@ -95,7 +96,7 @@ function ExchangeRateActionsBar({ filterCount <= 0 ? ( ) : ( - `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + `${filterCount} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/ExchangeRates/components.js b/client/src/containers/ExchangeRates/components.js index dc63374b8..4f38f04c3 100644 --- a/client/src/containers/ExchangeRates/components.js +++ b/client/src/containers/ExchangeRates/components.js @@ -8,7 +8,7 @@ import { MenuDivider, Intent, } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Icon, Money } from 'components'; import moment from 'moment'; import { safeCallback } from 'utils'; @@ -20,18 +20,18 @@ export function ActionMenuList({ row: { original }, payload: { onEditExchangeRate, onDeleteExchangeRate }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'edit_exchange_rate' })} + text={intl.get('edit_exchange_rate')} onClick={safeCallback(onEditExchangeRate, original)} /> } @@ -55,26 +55,26 @@ export function TableActionsCell(props) { } export function useExchangeRatesTableColumns() { - const { formatMessage } = useIntl(); + return useMemo( () => [ { id: 'date', - Header: formatMessage({ id: 'date' }), + Header: intl.get('date'), accessor: (r) => moment(r.date).format('YYYY MMM DD'), width: 150, }, { id: 'currency_code', - Header: formatMessage({ id: 'currency_code' }), + Header: intl.get('currency_code'), accessor: 'currency_code', className: 'currency_code', width: 150, }, { id: 'exchange_rate', - Header: formatMessage({ id: 'exchange_rate' }), + Header: intl.get('exchange_rate'), accessor: (r) => ( ), @@ -89,6 +89,6 @@ export function useExchangeRatesTableColumns() { width: 50, }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFloatingActions.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFloatingActions.js index 36b41a5dc..c6d30be2f 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFloatingActions.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFloatingActions.js @@ -10,7 +10,7 @@ import { MenuItem, } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseForm.js b/client/src/containers/Expenses/ExpenseForm/ExpenseForm.js index c282c8ac6..a75fd5dd6 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseForm.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseForm.js @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { defaultTo, sumBy, isEmpty } from 'lodash'; import { Formik, Form } from 'formik'; import classNames from 'classnames'; @@ -44,7 +44,6 @@ function ExpenseForm({ } = useExpenseFormContext(); const isNewMode = !expenseId; - const { formatMessage } = useIntl(); // History context. const history = useHistory(); @@ -73,9 +72,7 @@ function ExpenseForm({ if (totalAmount <= 0) { AppToaster.show({ - message: formatMessage({ - id: 'amount_cannot_be_zero_or_empty', - }), + message: intl.get('amount_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); return; @@ -93,12 +90,10 @@ function ExpenseForm({ // Handle request success. const handleSuccess = (response) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_expense_has_been_created_successfully' - : 'the_expense_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_expense_has_been_created_successfully' + : 'the_expense_has_been_edited_successfully', { number: values.payment_account_id }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseForm.schema.js b/client/src/containers/Expenses/ExpenseForm/ExpenseForm.schema.js index 41d1f3306..d05661cb6 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseForm.schema.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseForm.schema.js @@ -1,27 +1,27 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; import { isBlank } from 'utils'; const Schema = Yup.object().shape({ - beneficiary: Yup.string().label(formatMessage({ id: 'beneficiary' })), + beneficiary: Yup.string().label(intl.get('beneficiary')), payment_account_id: Yup.number() .required() - .label(formatMessage({ id: 'payment_account_' })), + .label(intl.get('payment_account_')), payment_date: Yup.date() .required() - .label(formatMessage({ id: 'payment_date_' })), + .label(intl.get('payment_date_')), reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), currency_code: Yup.string() .nullable() .max(3) - .label(formatMessage({ id: 'currency_code' })), + .label(intl.get('currency_code')), description: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) .nullable() - .label(formatMessage({ id: 'description' })), + .label(intl.get('description')), publish: Yup.boolean(), categories: Yup.array().of( Yup.object().shape({ diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.js index daef5b3e7..f60532e33 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFormFooter.js @@ -1,7 +1,7 @@ import React from 'react'; import { FastField } from 'formik'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { inputIntent } from 'utils'; import { Row, Dragzone, Col, Postbox } from 'components'; @@ -10,7 +10,7 @@ import { CLASSES } from 'common/classes'; export default function ExpenseFormFooter() { return (
- + } defaultOpen={false}> @@ -31,7 +31,7 @@ export default function ExpenseFormFooter() { initialFiles={[]} // onDrop={handleDropFiles} // onDeleteFile={handleDeleteFile} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} /> diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeader.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeader.js index 5ce47164a..609c6d2e4 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeader.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeader.js @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; @@ -21,7 +22,7 @@ export default function ExpenseFormHeader() {
} amount={totalExpenseAmount} currencyCode={values?.currency_code} /> diff --git a/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeaderFields.js b/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeaderFields.js index c72b19b2f..6d8739ad5 100644 --- a/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeaderFields.js +++ b/client/src/containers/Expenses/ExpenseForm/ExpenseFormHeaderFields.js @@ -2,7 +2,7 @@ import React from 'react'; import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; import { FastField, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; import { diff --git a/client/src/containers/Expenses/ExpenseForm/components.js b/client/src/containers/Expenses/ExpenseForm/components.js index 25bd34913..dfd0c96d2 100644 --- a/client/src/containers/Expenses/ExpenseForm/components.js +++ b/client/src/containers/Expenses/ExpenseForm/components.js @@ -1,8 +1,8 @@ import React from 'react'; import { Button, Tooltip, Intent, Position } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Icon, Hint } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { InputGroupCell, MoneyFieldCell, @@ -60,22 +60,21 @@ function AmountFooterCell({ payload: { currencyCode }, rows }) { /** * Expense amount header cell. */ - export function ExpenseAmountHeaderCell({ payload: { currencyCode } }) { - return formatMessage({ id: 'amount_currency' }, { currency: currencyCode }); +export function ExpenseAmountHeaderCell({ payload: { currencyCode } }) { + return intl.get('amount_currency', { currency: currencyCode }); } /** * Expense account footer cell. */ function ExpenseAccountFooterCell() { - return 'Total'; + return ; } /** * Retrieve expense form table entries columns. */ export function useExpenseFormTableColumns() { - return React.useMemo( () => [ { @@ -108,7 +107,7 @@ export function useExpenseFormTableColumns() { className: 'amount', }, { - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', Cell: InputGroupCell, disableSortBy: true, @@ -125,6 +124,6 @@ export function useExpenseFormTableColumns() { width: 45, }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Expenses/ExpenseForm/utils.js b/client/src/containers/Expenses/ExpenseForm/utils.js index 741052db2..a2a045abd 100644 --- a/client/src/containers/Expenses/ExpenseForm/utils.js +++ b/client/src/containers/Expenses/ExpenseForm/utils.js @@ -1,6 +1,6 @@ import { AppToaster } from 'components'; import moment from 'moment'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { transformToForm, repeatValue } from 'utils'; const ERROR = { @@ -14,9 +14,7 @@ export const transformErrors = (errors, { setErrors }) => { if (hasError(ERROR.EXPENSE_ALREADY_PUBLISHED)) { setErrors( AppToaster.show({ - message: formatMessage({ - id: 'the_expense_is_already_published', - }), + message: intl.get('the_expense_is_already_published'), }), ); } diff --git a/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js b/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js index 968d348a1..c1dd37718 100644 --- a/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js +++ b/client/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.js @@ -12,7 +12,7 @@ import { } from '@blueprintjs/core'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import withDialogActions from 'containers/Dialog/withDialogActions'; @@ -81,7 +81,7 @@ function ExpensesActionsBar({ className={classNames(Classes.MINIMAL, 'button--filter', { 'has-active-filters': filterCount > 0, })} - text="Filter" + text={} icon={} /> diff --git a/client/src/containers/Expenses/ExpensesLanding/ExpensesEmptyStatus.js b/client/src/containers/Expenses/ExpensesLanding/ExpensesEmptyStatus.js index b48c5e79b..5ce593bf0 100644 --- a/client/src/containers/Expenses/ExpensesLanding/ExpensesEmptyStatus.js +++ b/client/src/containers/Expenses/ExpensesLanding/ExpensesEmptyStatus.js @@ -2,17 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function InvoicesEmptyStatus() { const history = useHistory(); return ( } description={

- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. +

} action={ @@ -24,11 +24,11 @@ export default function InvoicesEmptyStatus() { history.push('/expenses/new'); }} > - New expense + } diff --git a/client/src/containers/Expenses/ExpensesLanding/components.js b/client/src/containers/Expenses/ExpensesLanding/components.js index 5edb4644a..25a91a252 100644 --- a/client/src/containers/Expenses/ExpensesLanding/components.js +++ b/client/src/containers/Expenses/ExpensesLanding/components.js @@ -12,9 +12,9 @@ import { MenuDivider, } from '@blueprintjs/core'; import moment from 'moment'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Money, Icon, If } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { safeCallback } from 'utils'; /** @@ -46,25 +46,25 @@ export function ActionsMenu({ } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} onClick={safeCallback(onViewDetails, original)} /> } - text={formatMessage({ id: 'publish_expense' })} + text={intl.get('publish_expense')} onClick={safeCallback(onPublish, original)} /> } - text={formatMessage({ id: 'edit_expense' })} + text={intl.get('edit_expense')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'delete_expense' })} + text={intl.get('delete_expense')} intent={Intent.DANGER} onClick={safeCallback(onDelete, original)} /> @@ -134,28 +134,28 @@ export function useExpensesTableColumns() { () => [ { id: 'payment_date', - Header: formatMessage({ id: 'payment_date' }), + Header: intl.get('payment_date'), accessor: (r) => moment(r.payment_date).format('YYYY MMM DD'), width: 140, className: 'payment_date', }, { id: 'amount', - Header: formatMessage({ id: 'full_amount' }), + Header: intl.get('full_amount'), accessor: TotalAmountAccessor, className: 'amount', width: 150, }, { id: 'payment_account', - Header: formatMessage({ id: 'payment_account' }), + Header: intl.get('payment_account'), accessor: 'payment_account.name', className: 'payment_account', width: 150, }, { id: 'expense_account', - Header: formatMessage({ id: 'expense_account' }), + Header: intl.get('expense_account'), accessor: ExpenseAccountAccessor, width: 160, className: 'expense_account', @@ -163,14 +163,14 @@ export function useExpensesTableColumns() { }, { id: 'published', - Header: formatMessage({ id: 'publish' }), + Header: intl.get('publish'), accessor: PublishAccessor, width: 100, className: 'publish', }, { id: 'description', - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: DescriptionAccessor, width: 150, className: 'description', diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js index 0e475cfa8..95c11fd28 100644 --- a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryActionsBar.js @@ -9,7 +9,7 @@ import { Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeader.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeader.js index 6ad67965e..363014256 100644 --- a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeader.js +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeader.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Formik, Form } from 'formik'; import * as Yup from 'yup'; import moment from 'moment'; diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js index a6b8589fb..9f065d3fc 100644 --- a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryHeaderGeneral.js @@ -8,7 +8,7 @@ import { Position, Classes, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { ContactsMultiSelect, Row, Col, FieldHint } from 'components'; import { useAPAgingSummaryContext } from './APAgingSummaryProvider'; diff --git a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js index 7ef048ae4..36fd8daae 100644 --- a/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js +++ b/client/src/containers/FinancialStatements/APAgingSummary/APAgingSummaryTable.js @@ -1,5 +1,6 @@ import React, { useCallback } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { DataTable } from 'components'; import FinancialSheet from 'components/FinancialSheet'; @@ -13,7 +14,7 @@ export default function APAgingSummaryTable({ //#ownProps organizationName, }) { - const { formatMessage } = useIntl(); + // AP aging summary report content. const { @@ -30,7 +31,7 @@ export default function APAgingSummaryTable({ diff --git a/client/src/containers/FinancialStatements/APAgingSummary/components.js b/client/src/containers/FinancialStatements/APAgingSummary/components.js index 89b054997..fca3ab6a6 100644 --- a/client/src/containers/FinancialStatements/APAgingSummary/components.js +++ b/client/src/containers/FinancialStatements/APAgingSummary/components.js @@ -1,7 +1,8 @@ import React, { useMemo } from 'react'; +import intl from 'react-intl-universal'; import { useAPAgingSummaryContext } from './APAgingSummaryProvider'; import { getColumnWidth } from 'utils'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { If } from 'components'; import FinancialLoadingBar from '../FinancialLoadingBar'; @@ -16,7 +17,9 @@ export const useAPAgingSummaryColumns = () => { const agingColumns = React.useMemo(() => { return columns.map( (agingColumn) => - `${agingColumn.before_days} - ${agingColumn.to_days || 'And Over'}`, + `${agingColumn.before_days} - ${ + agingColumn.to_days || intl.get('and_over') + }`, ); }, [columns]); @@ -60,14 +63,12 @@ export const useAPAgingSummaryColumns = () => { /** * A/P aging summary sheet loading bar. */ - export function APAgingSummarySheetLoadingBar() { - const { - isAPAgingFetching - } = useAPAgingSummaryContext(); +export function APAgingSummarySheetLoadingBar() { + const { isAPAgingFetching } = useAPAgingSummaryContext(); return ( - ) -} \ No newline at end of file + ); +} diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js index 3618b14bb..9215459c8 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js index 8b2e0ec18..03ab11063 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeader.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Formik, Form } from 'formik'; import * as Yup from 'yup'; import moment from 'moment'; diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js index 5bb68fc9f..f604b6693 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryHeaderGeneral.js @@ -8,7 +8,7 @@ import { Position, Classes, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { ContactsMultiSelect, Row, Col, FieldHint } from 'components'; import { momentFormatter } from 'utils'; diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js index 5f24d6f78..595e74271 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/ARAgingSummaryTable.js @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import DataTable from 'components/DataTable'; import FinancialSheet from 'components/FinancialSheet'; @@ -13,7 +13,7 @@ export default function ReceivableAgingSummaryTable({ // #ownProps organizationName, }) { - const { formatMessage } = useIntl(); + // AR aging summary report context. const { ARAgingSummary, isARAgingLoading } = useARAgingSummaryContext(); @@ -31,7 +31,7 @@ export default function ReceivableAgingSummaryTable({ diff --git a/client/src/containers/FinancialStatements/ARAgingSummary/components.js b/client/src/containers/FinancialStatements/ARAgingSummary/components.js index b78f92ee9..b0e4023bd 100644 --- a/client/src/containers/FinancialStatements/ARAgingSummary/components.js +++ b/client/src/containers/FinancialStatements/ARAgingSummary/components.js @@ -1,7 +1,8 @@ import React from 'react'; +import intl from 'react-intl-universal'; import { useARAgingSummaryContext } from './ARAgingSummaryProvider'; import { getColumnWidth } from 'utils'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { If } from 'components'; import FinancialLoadingBar from '../FinancialLoadingBar'; @@ -16,7 +17,9 @@ export const useARAgingSummaryColumns = () => { const agingColumns = React.useMemo(() => { return columns.map( (agingColumn) => - `${agingColumn.before_days} - ${agingColumn.to_days || 'And Over'}`, + `${agingColumn.before_days} - ${ + agingColumn.to_days || intl.get('and_over') + }`, ); }, [columns]); @@ -62,14 +65,12 @@ export const useARAgingSummaryColumns = () => { /** * A/R aging summary sheet loading bar. */ - export function ARAgingSummarySheetLoadingBar() { - const { - isARAgingFetching, - } = useARAgingSummaryContext(); +export function ARAgingSummarySheetLoadingBar() { + const { isARAgingFetching } = useARAgingSummaryContext(); return ( - ) -} \ No newline at end of file + ); +} diff --git a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.js b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.js index 83d9a21b0..d53f4e592 100644 --- a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.js +++ b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetHeader.js b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetHeader.js index 77ad25cea..30474ddc4 100644 --- a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetHeader.js +++ b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetHeader.js @@ -1,6 +1,7 @@ import React from 'react'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import moment from 'moment'; import * as Yup from 'yup'; import { Formik, Form } from 'formik'; @@ -27,8 +28,6 @@ function BalanceSheetHeader({ // #withBalanceSheetActions toggleBalanceSheetFilterDrawer: toggleFilterDrawer, }) { - const { formatMessage } = useIntl(); - // Filter form initial values. const initialValues = { basis: 'cash', @@ -42,11 +41,11 @@ function BalanceSheetHeader({ dateRange: Yup.string().optional(), fromDate: Yup.date() .required() - .label(formatMessage({ id: 'fromDate' })), + .label(intl.get('fromDate')), toDate: Yup.date() .min(Yup.ref('fromDate')) .required() - .label(formatMessage({ id: 'toDate' })), + .label(intl.get('toDate')), accountsFilter: Yup.string(), displayColumnsType: Yup.string(), }); diff --git a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js index 36af3bc3d..38aa54e26 100644 --- a/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js +++ b/client/src/containers/FinancialStatements/BalanceSheet/BalanceSheetTable.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import FinancialSheet from 'components/FinancialSheet'; @@ -16,7 +16,7 @@ export default function BalanceSheetTable({ // #ownProps companyName, }) { - const { formatMessage } = useIntl(); + // Balance sheet context. const { @@ -27,7 +27,7 @@ export default function BalanceSheetTable({ const tableColumns = useMemo( () => [ { - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), className: 'account_name', textOverview: true, @@ -36,7 +36,7 @@ export default function BalanceSheetTable({ ...(query.display_columns_type === 'total' ? [ { - Header: formatMessage({ id: 'total' }), + Header: intl.get('total'), accessor: 'total.formatted_amount', Cell: CellTextSpan, className: 'total', @@ -59,7 +59,7 @@ export default function BalanceSheetTable({ })) : []), ], - [query, columns, tableRows, formatMessage], + [query, columns, tableRows], ); // Calculates the default expanded rows of balance sheet table. @@ -83,7 +83,7 @@ export default function BalanceSheetTable({ { refetchBalanceSheet(); }; // Can't display any error if the report is loading. - if (isLoading) { return null; } + if (isLoading) { + return null; + } return (
- Just a moment! We're - calculating your cost transactions and this doesn't take much time. - Please check after sometime.{' '} - + {' '} +
@@ -40,13 +38,11 @@ export function BalanceSheetAlerts() { * Balance sheet loading bar. */ export function BalanceSheetLoadingBar() { - const { - isFetching - } = useBalanceSheetContext(); + const { isFetching } = useBalanceSheetContext(); return ( - ) -} \ No newline at end of file + ); +} diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatement.js b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatement.js new file mode 100644 index 000000000..514e4838d --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatement.js @@ -0,0 +1,92 @@ +import React, { useState, useEffect } from 'react'; +import moment from 'moment'; +import 'style/pages/FinancialStatements/CashFlowStatement.scss'; + +import { FinancialStatement } from 'components'; +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; + +import CashFlowStatementHeader from './CashFlowStatementHeader'; +import CashFlowStatementTable from './CashFlowStatementTable'; +import CashFlowStatementActionsBar from './CashFlowStatementActionsBar'; + +import withSettings from 'containers/Settings/withSettings'; +import withCashFlowStatementActions from './withCashFlowStatementActions'; +import { CashFlowStatementProvider } from './CashFlowStatementProvider'; +import { + CashFlowStatementLoadingBar, + CashFlowStatementAlerts, +} from './components'; + +import { compose } from 'utils'; + +/** + * Cash flow statement. + */ +function CashFlowStatement({ + // #withPreferences + organizationName, + //#withCashStatementActions + toggleCashFlowStatementFilterDrawer, +}) { + // filter + const [filter, setFilter] = useState({ + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().endOf('year').format('YYYY-MM-DD'), + basis: 'cash', + displayColumnsType: 'total', + }); + + // Handle refetch cash flow after filter change. + const handleFilterSubmit = (filter) => { + const _filter = { + ...filter, + fromDate: moment(filter.fromDate).format('YYYY-MM-DD'), + toDate: moment(filter.toDate).format('YYYY-MM-DD'), + }; + setFilter({ ..._filter }); + }; + + // Handle format number submit. + const handleNumberFormatSubmit = (values) => { + setFilter({ + ...filter, + numberFormat: values, + }); + }; + + useEffect( + () => () => { + toggleCashFlowStatementFilterDrawer(false); + }, + [toggleCashFlowStatementFilterDrawer], + ); + + return ( + + + + + + + +
+ +
+
+
+
+ ); +} + +export default compose( + withSettings(({ organizationSettings }) => ({ + organizationName: organizationSettings?.name, + })), + withCashFlowStatementActions, +)(CashFlowStatement); diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementActionsBar.js b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementActionsBar.js new file mode 100644 index 000000000..5faa5f65f --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementActionsBar.js @@ -0,0 +1,134 @@ +import React from 'react'; +import { + NavbarGroup, + NavbarDivider, + Button, + Classes, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import { FormattedMessage as T } from 'components'; +import classNames from 'classnames'; + +import { Icon } from 'components'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import NumberFormatDropdown from 'components/NumberFormatDropdown'; + +import { useCashFlowStatementContext } from './CashFlowStatementProvider'; +import withCashFlowStatement from './withCashFlowStatement'; +import withCashFlowStatementActions from './withCashFlowStatementActions'; + +import { compose, saveInvoke } from 'utils'; + +/** + * Cash flow statement actions bar. + */ +function CashFlowStatementActionsBar({ + //#withCashFlowStatement + isFilterDrawerOpen, + + //#withCashStatementActions + toggleCashFlowStatementFilterDrawer, + + //#ownProps + numberFormat, + onNumberFormatSubmit, +}) { + const { isCashFlowLoading, refetchCashFlow } = useCashFlowStatementContext(); + + // Handle filter toggle click. + const handleFilterToggleClick = () => { + toggleCashFlowStatementFilterDrawer(); + }; + + // Handle recalculate report button. + const handleRecalculateReport = () => { + refetchCashFlow(); + }; + + // handle number format form submit. + const handleNumberFormatSubmit = (values) => + saveInvoke(onNumberFormatSubmit, values); + + return ( + + + + +
+ + + + ); +} + +export default compose( + withCashFlowStatement(({ cashFlowStatementDrawerFilter }) => ({ + isFilterDrawerOpen: cashFlowStatementDrawerFilter, + })), + withCashFlowStatementActions, +)(CashFlowStatementHeader); diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementProvider.js b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementProvider.js new file mode 100644 index 000000000..c7ffa3ea8 --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementProvider.js @@ -0,0 +1,45 @@ +import React from 'react'; +import FinancialReportPage from '../FinancialReportPage'; +import { useCashFlowStatementReport } from 'hooks/query'; +import { transformFilterFormToQuery } from '../common'; + +const CashFLowStatementContext = React.createContext(); + +/** + * Cash flow statement provider. + */ +function CashFlowStatementProvider({ filter, ...props }) { + // transforms the given filter to query. + const query = React.useMemo( + () => transformFilterFormToQuery(filter), + [filter], + ); + + // fetch the cash flow statement report. + const { + data: cashFlowStatement, + isFetching: isCashFlowFetching, + isLoading: isCashFlowLoading, + refetch: refetchCashFlow, + } = useCashFlowStatementReport(query, { keepPreviousData: true }); + + const provider = { + cashFlowStatement, + isCashFlowFetching, + isCashFlowLoading, + refetchCashFlow, + query, + filter, + }; + + return ( + + + + ); +} + +const useCashFlowStatementContext = () => + React.useContext(CashFLowStatementContext); + +export { CashFlowStatementProvider, useCashFlowStatementContext }; diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementTable.js b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementTable.js new file mode 100644 index 000000000..166151406 --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/CashFlowStatementTable.js @@ -0,0 +1,63 @@ +import React, { useMemo } from 'react'; +import intl from 'react-intl-universal'; + +import { DataTable } from 'components'; +import FinancialSheet from 'components/FinancialSheet'; +import { useCashFlowStatementColumns } from './components'; +import { useCashFlowStatementContext } from './CashFlowStatementProvider'; + +import { defaultExpanderReducer } from 'utils'; + +/** + * Cash flow statement table. + */ +export default function CashFlowStatementTable({ + // #ownProps + companyName, +}) { + + + const { + cashFlowStatement: { tableRows }, + isCashFlowLoading, + query, + } = useCashFlowStatementContext(); + + const columns = useCashFlowStatementColumns(); + + const expandedRows = useMemo( + () => defaultExpanderReducer(tableRows, 4), + [tableRows], + ); + + const rowClassNames = (row) => { + return [ + `row-type--${row.original.rowTypes}`, + `row-type--${row.original.id}`, + ]; + }; + + return ( + + + + ); +} diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/components.js b/client/src/containers/FinancialStatements/CashFlowStatement/components.js new file mode 100644 index 000000000..865825710 --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/components.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { Button } from '@blueprintjs/core'; +import { Icon, If } from 'components'; +import { FormattedMessage as T } from 'components'; + +import { dynamicColumns } from './utils'; +import { useCashFlowStatementContext } from './CashFlowStatementProvider'; +import FinancialLoadingBar from '../FinancialLoadingBar'; + +/** + * Retrieve cash flow statement columns. + */ +export const useCashFlowStatementColumns = () => { + const { + cashFlowStatement: { columns, tableRows }, + } = useCashFlowStatementContext(); + + return React.useMemo( + () => dynamicColumns(columns, tableRows), + [columns, tableRows], + ); +}; + +/** + * Cash flow statement loading bar. + */ +export function CashFlowStatementLoadingBar() { + const { isCashFlowLoading } = useCashFlowStatementContext(); + return ( + + + + ); +} + +/** + * Cash flow statement alter + */ +export function CashFlowStatementAlerts() { + const { cashFlowStatement, isCashFlowLoading, refetchCashFlow } = + useCashFlowStatementContext(); + + // Handle refetch the report sheet. + const handleRecalcReport = () => { + refetchCashFlow(); + }; + + // Can't display any error if the report is loading + if (isCashFlowLoading) { + return null; + } + + return ( + +
+ + + +
+
+ ); +} diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/utils.js b/client/src/containers/FinancialStatements/CashFlowStatement/utils.js new file mode 100644 index 000000000..95086ef54 --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/utils.js @@ -0,0 +1,68 @@ +import * as R from 'ramda'; +import { CellTextSpan } from 'components/Datatable/Cells'; +import { getColumnWidth } from 'utils'; +import intl from 'react-intl-universal'; + +/** + * Account name column mapper. + */ +const accountNameMapper = (column) => ({ + id: column.key, + key: column.key, + Header: intl.get('account_name'), + accessor: 'cells[0].value', + className: 'account_name', + textOverview: true, + width: 240, + disableSortBy: true, +}); + +/** + * Date range columns mapper. + */ +const dateRangeMapper = (data, index, column) => ({ + id: column.key, + Header: column.label, + key: column.key, + accessor: `cells[${index}].value`, + width: getColumnWidth(data, `cells.${index}.value`, { minWidth: 100 }), + className: `date-period ${column.key}`, + disableSortBy: true, + textOverview: true, +}); + +/** + * Total column mapper. + */ +const totalMapper = (data, index, column) => ({ + key: 'total', + Header: intl.get('total'), + accessor: `cells[${index}].value`, + className: 'total', + textOverview: true, + Cell: CellTextSpan, + width: getColumnWidth(data, `cells[${index}].value`, { minWidth: 100 }), + disableSortBy: true, +}); + +/** + * Detarmines the given string starts with `date-range` string. + */ +const isMatchesDateRange = (r) => R.match(/^date-range/g, r).length > 0; + +/** + * Cash flow dynamic columns. + */ +export const dynamicColumns = (columns, data) => { + const mapper = (column, index) => { + return R.compose( + R.when( + R.pathSatisfies(isMatchesDateRange, ['key']), + R.curry(dateRangeMapper)(data, index), + ), + R.when(R.pathEq(['key'], 'name'), accountNameMapper), + R.when(R.pathEq(['key'], 'total'), R.curry(totalMapper)(data, index)), + )(column); + }; + return columns.map(mapper); +}; diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatement.js b/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatement.js new file mode 100644 index 000000000..ab3d6519a --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatement.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { getCashFlowStatementFilterDrawer } from 'store/financialStatement/financialStatements.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + const mapped = { + cashFlowStatementDrawerFilter: getCashFlowStatementFilterDrawer(state), + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatementActions.js b/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatementActions.js new file mode 100644 index 000000000..689d7ac62 --- /dev/null +++ b/client/src/containers/FinancialStatements/CashFlowStatement/withCashFlowStatementActions.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { toggleCashFlowStatementFilterDrawer } from 'store/financialStatement/financialStatements.actions'; + +const mapDispatchToProps = (dispatch) => ({ + toggleCashFlowStatementFilterDrawer: (toggle) => + dispatch(toggleCashFlowStatementFilterDrawer(toggle)), +}); + +export default connect(null, mapDispatchToProps); diff --git a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryActionsBar.js b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryActionsBar.js index 7ee8f97b9..1ea7e252d 100644 --- a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryActionsBar.js +++ b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryGeneralPanel.js b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryGeneralPanel.js index 9ecdd7aa5..8b0b5c221 100644 --- a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryGeneralPanel.js +++ b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryGeneralPanel.js @@ -7,7 +7,7 @@ import { Classes, Checkbox, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Row, Col, FieldHint } from 'components'; import { @@ -57,7 +57,7 @@ export default function CustomersBalanceSummaryGeneralPanel() { inline={true} name={'percentage'} small={true} - label={'Percentage Of Column'} + label={} {...field} /> diff --git a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryHeader.js b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryHeader.js index 6e1b3ec58..64506fa20 100644 --- a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryHeader.js +++ b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryHeader.js @@ -3,7 +3,8 @@ import * as Yup from 'yup'; import { Formik, Form } from 'formik'; import moment from 'moment'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import withCustomersBalanceSummary from './withCustomersBalanceSummary'; diff --git a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryTable.js b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryTable.js index 57ee2655f..6eb727d9d 100644 --- a/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryTable.js +++ b/client/src/containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummaryTable.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import FinancialSheet from 'components/FinancialSheet'; @@ -15,7 +15,7 @@ export default function CustomersBalanceSummaryTable({ // #ownProps companyName, }) { - const { formatMessage } = useIntl(); + const { isCustomersBalanceLoading, @@ -32,7 +32,7 @@ export default function CustomersBalanceSummaryTable({ diff --git a/client/src/containers/FinancialStatements/CustomersBalanceSummary/components.js b/client/src/containers/FinancialStatements/CustomersBalanceSummary/components.js index 0ac61d5c9..e8ab66769 100644 --- a/client/src/containers/FinancialStatements/CustomersBalanceSummary/components.js +++ b/client/src/containers/FinancialStatements/CustomersBalanceSummary/components.js @@ -1,5 +1,5 @@ import React from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { If } from 'components'; import FinancialLoadingBar from '../FinancialLoadingBar'; @@ -12,25 +12,25 @@ export const useCustomersSummaryColumns = () => { return React.useMemo( () => [ { - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'cells[0].value', className: 'customer_name', width: 240, }, { - Header: formatMessage({ id: 'total' }), + Header: intl.get('total'), accessor: 'cells[1].value', className: 'total', width: 140, }, { - Header: formatMessage({ id: 'percentage_of_column' }), + Header: intl.get('percentage_of_column'), accessor: 'cells[2].value', className: 'total', width: 140, }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsActionsBar.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsActionsBar.js index 3c8ec4f73..5fc23ed10 100644 --- a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsActionsBar.js +++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js index f766d9efe..4f3e8e77b 100644 --- a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js +++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsHeader.js @@ -1,6 +1,7 @@ import React from 'react'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import moment from 'moment'; import * as Yup from 'yup'; import { Formik, Form } from 'formik'; @@ -27,7 +28,7 @@ function CustomersTransactionsHeader({ //#withCustomersTransactionsActions toggleCustomersTransactionsFilterDrawer: toggleFilterDrawer, }) { - const { formatMessage } = useIntl(); + // Filter form initial values. const initialValues = { @@ -40,11 +41,11 @@ function CustomersTransactionsHeader({ const validationSchema = Yup.object().shape({ fromDate: Yup.date() .required() - .label(formatMessage({ id: 'fromDate' })), + .label(intl.get('fromDate')), toDate: Yup.date() .min(Yup.ref('fromDate')) .required() - .label(formatMessage({ id: 'toDate' })), + .label(intl.get('toDate')), }); // Handle form submit. diff --git a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js index 100395693..1215da2f7 100644 --- a/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js +++ b/client/src/containers/FinancialStatements/CustomersTransactions/CustomersTransactionsTable.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import FinancialSheet from 'components/FinancialSheet'; @@ -16,7 +16,7 @@ export default function CustomersTransactionsTable({ // #ownProps companyName, }) { - const { formatMessage } = useIntl(); + const { customersTransactions: { tableRows }, @@ -38,7 +38,7 @@ export default function CustomersTransactionsTable({ { const { customersTransactions: { tableRows }, @@ -17,7 +18,7 @@ export const useCustomersTransactionsColumns = () => { return React.useMemo( () => [ { - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: ({ cells }) => { return ( { textOverview: true, }, { - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: 'cells[1].value', className: 'name', textOverview: true, width: 170, }, { - Header: formatMessage({ id: 'reference_type' }), + Header: intl.get('reference_type'), accessor: 'cells[2].value', width: 120, textOverview: true, }, { - Header: formatMessage({ id: 'transaction_type' }), + Header: intl.get('transaction_type'), accessor: 'cells[3].value', width: 120, textOverview: true, }, { - Header: formatMessage({ id: 'credit' }), + Header: intl.get('credit'), accessor: 'cells[4].value', className: 'credit', textOverview: true, @@ -61,7 +62,7 @@ export const useCustomersTransactionsColumns = () => { }), }, { - Header: formatMessage({ id: 'debit' }), + Header: intl.get('debit'), accessor: 'cells[5].value', className: 'debit', textOverview: true, @@ -71,7 +72,7 @@ export const useCustomersTransactionsColumns = () => { }), }, { - Header: formatMessage({ id: 'running_balance' }), + Header: intl.get('running_balance'), accessor: 'cells[6].value', className: 'running_balance', textOverview: true, @@ -81,7 +82,7 @@ export const useCustomersTransactionsColumns = () => { }), }, ], - [tableRows, formatMessage], + [tableRows], ); }; diff --git a/client/src/containers/FinancialStatements/FinancialAccountsFilter.js b/client/src/containers/FinancialStatements/FinancialAccountsFilter.js index 6b50c3aa8..f640aee5f 100644 --- a/client/src/containers/FinancialStatements/FinancialAccountsFilter.js +++ b/client/src/containers/FinancialStatements/FinancialAccountsFilter.js @@ -6,7 +6,7 @@ import { Position, FormGroup, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { FastField } from 'formik'; diff --git a/client/src/containers/FinancialStatements/FinancialStatementDateRange.js b/client/src/containers/FinancialStatements/FinancialStatementDateRange.js index c68c9ac2c..1d882996f 100644 --- a/client/src/containers/FinancialStatements/FinancialStatementDateRange.js +++ b/client/src/containers/FinancialStatements/FinancialStatementDateRange.js @@ -5,14 +5,14 @@ import moment from 'moment'; import { Row, Col, Hint } from 'components'; import { momentFormatter, parseDateRangeQuery } from 'utils'; import { DateInput } from '@blueprintjs/datetime'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { dateRangeOptions } from 'containers/FinancialStatements/common'; /** * Financial statement - Date range select. */ export default function FinancialStatementDateRange() { - const { formatMessage } = useIntl(); + return ( <> @@ -24,7 +24,7 @@ export default function FinancialStatementDateRange() { field: { value }, }) => ( } minimal={true} fill={true} @@ -62,7 +62,7 @@ export default function FinancialStatementDateRange() { meta: { error, touched }, }) => ( } fill={true} intent={error && Intent.DANGER} @@ -92,7 +92,7 @@ export default function FinancialStatementDateRange() { meta: { error }, }) => ( } fill={true} intent={error && Intent.DANGER} diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js index 8f9c59386..2a9e7ee52 100644 --- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js +++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js index b86b2dd2d..54545999f 100644 --- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js +++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeader.js @@ -3,7 +3,7 @@ import moment from 'moment'; import * as Yup from 'yup'; import { Formik, Form } from 'formik'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import GeneralLedgerHeaderGeneralPane from './GeneralLedgerHeaderGeneralPane'; diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js index 694362c08..b50d329fd 100644 --- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js +++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerHeaderGeneralPane.js @@ -3,7 +3,7 @@ import { FormGroup, Classes, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { AccountsMultiSelect, Row, Col } from 'components'; diff --git a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerTable.js b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerTable.js index 8684c024d..fd8ee3f5e 100644 --- a/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerTable.js +++ b/client/src/containers/FinancialStatements/GeneralLedger/GeneralLedgerTable.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { defaultExpanderReducer } from 'utils'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; @@ -15,7 +15,7 @@ import { useGeneralLedgerTableColumns } from './components'; * General ledger table. */ export default function GeneralLedgerTable({ companyName }) { - const { formatMessage } = useIntl(); + // General ledger context. const { @@ -36,7 +36,7 @@ export default function GeneralLedgerTable({ companyName }) { return ( [ { - Header: formatMessage({ id: 'date' }), + Header: intl.get('date'), accessor: (row) => { if (row.rowType === 'ACCOUNT_ROW') { return ( @@ -38,43 +40,42 @@ export function useGeneralLedgerTableColumns() { width: 120, }, { - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: 'name', className: 'name', textOverview: true, // width: 200, }, { - Header: formatMessage({ id: 'transaction_type' }), + Header: intl.get('transaction_type'), accessor: 'reference_type_formatted', className: 'transaction_type', width: 125, textOverview: true, }, { - Header: formatMessage({ id: 'transaction_number' }), + Header: intl.get('transaction_number'), accessor: 'reference_id', className: 'transaction_number', width: 100, }, { - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'note', className: 'description', // width: 145, }, { - Header: formatMessage({ id: 'credit' }), + Header: intl.get('credit'), accessor: 'formatted_credit', className: 'credit', width: getColumnWidth(tableRows, 'formatted_credit', { - minWidth: 100, magicSpacing: 10, }), }, { - Header: formatMessage({ id: 'debit' }), + Header: intl.get('debit'), accessor: 'formatted_debit', className: 'debit', width: getColumnWidth(tableRows, 'formatted_debit', { @@ -83,7 +84,7 @@ export function useGeneralLedgerTableColumns() { }), }, { - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: 'formatted_amount', className: 'amount', width: getColumnWidth(tableRows, 'formatted_amount', { @@ -92,7 +93,7 @@ export function useGeneralLedgerTableColumns() { }), }, { - Header: formatMessage({ id: 'running_balance' }), + Header: intl.get('running_balance'), accessor: 'formatted_running_balance', className: 'running_balance', width: getColumnWidth(tableRows, 'formatted_running_balance', { @@ -101,37 +102,32 @@ export function useGeneralLedgerTableColumns() { }), }, ], - [formatMessage, tableRows], + [tableRows], ); } - /** * General ledger sheet alerts. */ - export function GeneralLedgerSheetAlerts() { - const { - generalLedger, - isLoading, - sheetRefresh - } = useGeneralLedgerContext(); +export function GeneralLedgerSheetAlerts() { + const { generalLedger, isLoading, sheetRefresh } = useGeneralLedgerContext(); // Handle refetch the report sheet. const handleRecalcReport = () => { sheetRefresh(); }; // Can't display any error if the report is loading. - if (isLoading) { return null; } + if (isLoading) { + return null; + } return (
- Just a moment! We're - calculating your cost transactions and this doesn't take much time. - Please check after sometime.{' '} - + +
@@ -142,13 +138,11 @@ export function useGeneralLedgerTableColumns() { * General ledger sheet loading bar. */ export function GeneralLedgerSheetLoadingBar() { - const { - isFetching, - } = useGeneralLedgerContext(); + const { isFetching } = useGeneralLedgerContext(); return ( - ) + ); } diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetails.js b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetails.js new file mode 100644 index 000000000..21d8159c5 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetails.js @@ -0,0 +1,87 @@ +import React, { useEffect, useState } from 'react'; +import moment from 'moment'; +import 'style/pages/FinancialStatements/InventoryItemDetails.scss'; + +import { FinancialStatement } from 'components'; +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; + +import InventoryItemDetailsActionsBar from './InventoryItemDetailsActionsBar'; +import InventoryItemDetailsHeader from './InventoryItemDetailsHeader'; +import InventoryItemDetailsTable from './InventoryItemDetailsTable'; + +import withInventoryItemDetailsActions from './withInventoryItemDetailsActions'; +import withSettings from 'containers/Settings/withSettings'; +import { InventoryItemDetailsProvider } from './InventoryItemDetailsProvider'; +import { + InventoryItemDetailsLoadingBar, + InventoryItemDetailsAlerts, +} from './components'; + +import { compose } from 'utils'; + +/** + * inventory item details. + */ +function InventoryItemDetails({ + // #withSettings + organizationName, + + //#withInventoryItemDetailsActions + toggleInventoryItemDetailsFilterDrawer: toggleFilterDrawer, +}) { + const [filter, setFilter] = useState({ + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().endOf('year').format('YYYY-MM-DD'), + }); + + const handleFilterSubmit = (filter) => { + const _filter = { + ...filter, + fromDate: moment(filter.fromDate).format('YYYY-MM-DD'), + toDate: moment(filter.toDate).format('YYYY-MM-DD'), + }; + setFilter({ ..._filter }); + }; + + // Handle number format submit. + const handleNumberFormatSubmit = (values) => { + setFilter({ + ...filter, + numberFormat: values, + }); + }; + + useEffect(() => () => toggleFilterDrawer(false), [toggleFilterDrawer]); + + return ( + + + + + + + +
+ +
+
+ +
+
+
+
+ ); +} + +export default compose( + withSettings(({ organizationSettings }) => ({ + organizationName: organizationSettings?.name, + })), + withInventoryItemDetailsActions, +)(InventoryItemDetails); diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsActionsBar.js b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsActionsBar.js new file mode 100644 index 000000000..abf86ce66 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsActionsBar.js @@ -0,0 +1,131 @@ +import React from 'react'; +import { + NavbarGroup, + Button, + Classes, + NavbarDivider, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import { FormattedMessage as T } from 'components'; +import classNames from 'classnames'; + +import { Icon } from 'components'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import NumberFormatDropdown from 'components/NumberFormatDropdown'; + +import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider'; +import withInventoryItemDetails from './withInventoryItemDetails'; +import withInventoryItemDetailsActions from './withInventoryItemDetailsActions'; + +import { compose, saveInvoke } from 'utils'; + +/** + * Inventory item details actions bar. + */ +function InventoryItemDetailsActionsBar({ + // #ownProps + numberFormat, + onNumberFormatSubmit, + + //#withInventoryItemDetails + isFilterDrawerOpen, + + //#withInventoryItemDetailsActions + toggleInventoryItemDetailsFilterDrawer: toggleFilterDrawer, +}) { + const { isInventoryItemDetailsLoading, inventoryItemDetailsRefetch } = + useInventoryItemDetailsContext(); + + // Handle filter toggle click. + const handleFilterToggleClick = () => { + toggleFilterDrawer(); + }; + //Handle recalculate the report button. + const handleRecalcReport = () => { + inventoryItemDetailsRefetch(); + }; + // Handle number format form submit. + const handleNumberFormatSubmit = (values) => { + saveInvoke(onNumberFormatSubmit, values); + }; + + return ( + + + + +
+ + + + ); +} + +export default compose( + withInventoryItemDetails(({ inventoryItemDetailDrawerFilter }) => ({ + isFilterDrawerOpen: inventoryItemDetailDrawerFilter, + })), + withInventoryItemDetailsActions, +)(InventoryItemDetailsHeader); diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsHeaderGeneralPanel.js b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsHeaderGeneralPanel.js new file mode 100644 index 000000000..b3aafc5fb --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsHeaderGeneralPanel.js @@ -0,0 +1,13 @@ +import React from 'react'; +import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange'; + +/** + * Inventory item details header - General panel. + */ +export default function InventoryItemDetailsHeaderGeneralPanel() { + return ( +
+ +
+ ); +} diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsProvider.js b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsProvider.js new file mode 100644 index 000000000..746fce999 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsProvider.js @@ -0,0 +1,43 @@ +import React from 'react'; +import FinancialReportPage from '../FinancialReportPage'; +import { useInventoryItemDetailsReport } from 'hooks/query'; +import { transformFilterFormToQuery } from '../common'; + +const InventoryItemDetailsContext = React.createContext(); + +/** + * Inventory item details provider. + */ +function InventoryItemDetailsProvider({ filter, ...props }) { + const query = React.useMemo( + () => transformFilterFormToQuery(filter), + [filter], + ); + + // fetch inventory item details. + const { + data: inventoryItemDetails, + isFetching: isInventoryItemDetailsFetching, + isLoading: isInventoryItemDetailsLoading, + refetch: inventoryItemDetailsRefetch, + } = useInventoryItemDetailsReport(query, { keepPreviousData: true }); + + const provider = { + inventoryItemDetails, + isInventoryItemDetailsFetching, + isInventoryItemDetailsLoading, + inventoryItemDetailsRefetch, + query, + filter, + }; + + return ( + + + + ); +} +const useInventoryItemDetailsContext = () => + React.useContext(InventoryItemDetailsContext); + +export { InventoryItemDetailsProvider, useInventoryItemDetailsContext }; diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsTable.js b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsTable.js new file mode 100644 index 000000000..b9840dd06 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/InventoryItemDetailsTable.js @@ -0,0 +1,59 @@ +import React, { useMemo, useCallback } from 'react'; +import intl from 'react-intl-universal'; + +import classNames from 'classnames'; + +import FinancialSheet from 'components/FinancialSheet'; +import { DataTable } from 'components'; +import { useInventoryItemDetailsColumns } from './components'; +import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider'; + +import { defaultExpanderReducer } from 'utils'; + +/** + * Inventory item detail table. + */ +export default function InventoryItemDetailsTable({ + // #ownProps + companyName, +}) { + const { + inventoryItemDetails: { tableRows }, + isInventoryItemDetailsLoading, + query, + } = useInventoryItemDetailsContext(); + + const columns = useInventoryItemDetailsColumns(); + + const expandedRows = useMemo( + () => defaultExpanderReducer(tableRows, 4), + [tableRows], + ); + + const rowClassNames = (row) => { + return [`row-type--${row.original.rowTypes}`]; + }; + + return ( + + + + ); +} diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/components.js b/client/src/containers/FinancialStatements/InventoryItemDetails/components.js new file mode 100644 index 000000000..ca7e71c5a --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/components.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { Button } from '@blueprintjs/core'; +import { Icon, If } from 'components'; +import { FormattedMessage as T } from 'components'; + +import { dynamicColumns } from './utils'; +import FinancialLoadingBar from '../FinancialLoadingBar'; +import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider'; + +/** + * Retrieve inventory item details columns. + */ +export const useInventoryItemDetailsColumns = () => { + const { + inventoryItemDetails: { columns, tableRows }, + } = useInventoryItemDetailsContext(); + + return React.useMemo( + () => dynamicColumns(columns, tableRows), + [columns, tableRows], + ); +}; + +/** + * inventory item details loading bar. + */ +export function InventoryItemDetailsLoadingBar() { + const { isInventoryItemDetailsLoading } = useInventoryItemDetailsContext(); + return ( + + + + ); +} + +/** + * inventory item details alerts + */ +export function InventoryItemDetailsAlerts() { + const { + inventoryItemDetails, + isInventoryItemDetailsLoading, + inventoryItemDetailsRefetch, + } = useInventoryItemDetailsContext(); + + // Handle refetch the report sheet. + const handleRecalcReport = () => { + inventoryItemDetailsRefetch(); + }; + + // Can't display any error if the report is loading + if (isInventoryItemDetailsLoading) { + return null; + } + + return ( + +
+ + + + +
+
+ ); +} diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/utils.js b/client/src/containers/FinancialStatements/InventoryItemDetails/utils.js new file mode 100644 index 000000000..e65fbf0a0 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/utils.js @@ -0,0 +1,43 @@ +import React from 'react'; +import * as R from 'ramda'; +import { getColumnWidth, getForceWidth } from 'utils'; + +/** + * columns mapper. + */ +const columnsMapper = (data, index, column) => ({ + id: column.key, + key: column.key, + Header: column.label, + accessor: ({ cells }) => { + return ( + + {cells[index]?.value} + + ); + }, + className: column.key, + width: getColumnWidth(data, `cells.${index}.key`, { + minWidth: 130, + magicSpacing: 10, + }), + disableSortBy: true, + // textOverview: true, +}); + +/** + * Inventory item details columns. + */ +export const dynamicColumns = (columns, data) => { + const mapper = (column, index) => { + return R.compose( + R.when(R.pathEq(['key']), R.curry(columnsMapper)(data, index)), + )(column); + }; + return columns.map(mapper); +}; diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetails.js b/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetails.js new file mode 100644 index 000000000..bcdeb7169 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetails.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import { getInventoryItemDetailsFilterDrawer } from 'store/financialStatement/financialStatements.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + const mapped = { + inventoryItemDetailDrawerFilter: getInventoryItemDetailsFilterDrawer( + state, + props, + ), + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetailsActions.js b/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetailsActions.js new file mode 100644 index 000000000..1daaa3eb5 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryItemDetails/withInventoryItemDetailsActions.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { toggleInventoryItemDetailsFilterDrawer } from 'store/financialStatement/financialStatements.actions'; + +const mapActionsToProps = (dispatch) => ({ + toggleInventoryItemDetailsFilterDrawer: (toggle) => + dispatch(toggleInventoryItemDetailsFilterDrawer(toggle)), +}); + +export default connect(null, mapActionsToProps); diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js index 52a700780..6a5e2f553 100644 --- a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js @@ -9,7 +9,7 @@ import { Position, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Icon } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -54,7 +54,7 @@ function InventoryValuationActionsBar({
); -} \ No newline at end of file +} diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js index d49e417c0..8d304358f 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossActionsBar.js @@ -8,7 +8,7 @@ import { Position, PopoverInteractionKind, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js index 9b8f2060d..307ac826b 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetHeader.js @@ -1,7 +1,8 @@ import React, { useEffect } from 'react'; import moment from 'moment'; import { Formik, Form } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import * as Yup from 'yup'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; @@ -24,17 +25,17 @@ function ProfitLossHeader({ // #withProfitLossActions toggleProfitLossFilterDrawer: toggleFilterDrawer, }) { - const { formatMessage } = useIntl(); + // Validation schema. const validationSchema = Yup.object().shape({ fromDate: Yup.date() .required() - .label(formatMessage({ id: 'from_date' })), + .label(intl.get('from_date')), toDate: Yup.date() .min(Yup.ref('fromDate')) .required() - .label(formatMessage({ id: 'to_date' })), + .label(intl.get('to_date')), accountsFilter: Yup.string(), displayColumnsType: Yup.string(), }); diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js index 6d7b8b486..cb1e78f51 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetTable.js @@ -1,5 +1,6 @@ import React, { useMemo, useCallback } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import FinancialSheet from 'components/FinancialSheet'; import DataTable from 'components/DataTable'; @@ -12,7 +13,7 @@ export default function ProfitLossSheetTable({ // #ownProps companyName, }) { - const { formatMessage } = useIntl(); + // Profit/Loss sheet context. const { @@ -23,7 +24,7 @@ export default function ProfitLossSheetTable({ const tableColumns = useMemo( () => [ { - Header: formatMessage({ id: 'account' }), + Header: intl.get('account'), accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), className: 'name', textOverview: true, @@ -32,7 +33,7 @@ export default function ProfitLossSheetTable({ ...(query.display_columns_type === 'total' ? [ { - Header: formatMessage({ id: 'total' }), + Header: intl.get('total'), Cell: CellTextSpan, accessor: 'total.formatted_amount', className: 'total', @@ -59,7 +60,6 @@ export default function ProfitLossSheetTable({ query.display_columns_type, tableRows, columns, - formatMessage, ], ); diff --git a/client/src/containers/FinancialStatements/ProfitLossSheet/components.js b/client/src/containers/FinancialStatements/ProfitLossSheet/components.js index 6e69b4438..bfc4b54f1 100644 --- a/client/src/containers/FinancialStatements/ProfitLossSheet/components.js +++ b/client/src/containers/FinancialStatements/ProfitLossSheet/components.js @@ -1,6 +1,8 @@ import React from 'react'; import { Button } from '@blueprintjs/core'; import { Icon, If } from 'components'; +import { FormattedMessage as T } from 'components'; + import { useProfitLossSheetContext } from './ProfitLossProvider'; import FinancialLoadingBar from '../FinancialLoadingBar'; @@ -21,11 +23,8 @@ export function ProfitLossSheetLoadingBar() { * Balance sheet alerts. */ export function ProfitLossSheetAlerts() { - const { - isLoading, - sheetRefetch, - profitLossSheet, - } = useProfitLossSheetContext(); + const { isLoading, sheetRefetch, profitLossSheet } = + useProfitLossSheetContext(); // Handle refetch the report sheet. const handleRecalcReport = () => { @@ -39,11 +38,11 @@ export function ProfitLossSheetAlerts() { return (
- Just a moment! We're - calculating your cost transactions and this doesn't take much time. - Please check after sometime.{' '} + + +
diff --git a/client/src/containers/FinancialStatements/PurchasesByItems/PurchasesByItemsActionsBar.js b/client/src/containers/FinancialStatements/PurchasesByItems/PurchasesByItemsActionsBar.js index 6bbd3d542..2842adafc 100644 --- a/client/src/containers/FinancialStatements/PurchasesByItems/PurchasesByItemsActionsBar.js +++ b/client/src/containers/FinancialStatements/PurchasesByItems/PurchasesByItemsActionsBar.js @@ -9,7 +9,7 @@ import { Position, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Icon } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -53,7 +53,7 @@ function PurchasesByItemsActionsBar({
- ) -} \ No newline at end of file + ); +} diff --git a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryActionsBar.js b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryActionsBar.js index f7f01655c..9d5f56d63 100644 --- a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryActionsBar.js +++ b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { Icon } from 'components'; diff --git a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeader.js b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeader.js index fb5be3c6f..9e331973a 100644 --- a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeader.js +++ b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeader.js @@ -3,7 +3,7 @@ import * as Yup from 'yup'; import { Formik, Form } from 'formik'; import moment from 'moment'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import withVendorsBalanceSummary from './withVendorsBalanceSummary'; diff --git a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeaderGeneral.js b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeaderGeneral.js index b7811721a..7ac062a36 100644 --- a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeaderGeneral.js +++ b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryHeaderGeneral.js @@ -2,7 +2,7 @@ import React from 'react'; import { FastField } from 'formik'; import { DateInput } from '@blueprintjs/datetime'; import { FormGroup, Position, Classes, Checkbox } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Row, Col, FieldHint } from 'components'; import { momentFormatter, @@ -50,7 +50,7 @@ export default function VendorsBalanceSummaryHeaderGeneral() { } name={'percentage'} {...field} /> diff --git a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryTable.js b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryTable.js index 043bd2c54..7cb510ab9 100644 --- a/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryTable.js +++ b/client/src/containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummaryTable.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { DataTable } from 'components'; import FinancialSheet from 'components/FinancialSheet'; @@ -13,7 +14,7 @@ export default function VendorsBalanceSummaryTable({ //#ownProps organizationName, }) { - const { formatMessage } = useIntl(); + const { VendorBalanceSummary, @@ -31,7 +32,7 @@ export default function VendorsBalanceSummaryTable({ diff --git a/client/src/containers/FinancialStatements/VendorsBalanceSummary/components.js b/client/src/containers/FinancialStatements/VendorsBalanceSummary/components.js index f6d9f5315..3ee5dc231 100644 --- a/client/src/containers/FinancialStatements/VendorsBalanceSummary/components.js +++ b/client/src/containers/FinancialStatements/VendorsBalanceSummary/components.js @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { If } from 'components'; import { getColumnWidth } from 'utils'; @@ -12,7 +12,7 @@ import { useVendorsBalanceSummaryContext } from './VendorsBalanceSummaryProvider export const useVendorsBalanceColumns = () => { return useMemo(() => [ { - Header: formatMessage({ id: 'vendor_name' }), + Header: intl.get('vendor_name'), accessor: 'cells[0].value', className: 'customer_name', width: 240, @@ -20,13 +20,13 @@ export const useVendorsBalanceColumns = () => { textOverview: true, }, { - Header: formatMessage({ id: 'total' }), + Header: intl.get('total'), accessor: 'cells[1].value', className: 'total', width: 140, }, { - Header: formatMessage({ id: 'percentage_of_column' }), + Header: intl.get('percentage_of_column'), accessor: 'cells[2].value', // className: 'total', width: 140, diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js index 115585ec3..9d4a9ff9c 100644 --- a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js +++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsActionsBar.js @@ -8,7 +8,7 @@ import { PopoverInteractionKind, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import Icon from 'components/Icon'; diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js index 297f9487a..babf5f7c8 100644 --- a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js +++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsHeader.js @@ -3,7 +3,8 @@ import * as Yup from 'yup'; import moment from 'moment'; import { Formik, Form } from 'formik'; import { Tabs, Tab, Button, Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader'; import VendorsTransactionsHeaderGeneralPanel from './VendorsTransactionsHeaderGeneralPanel'; @@ -28,7 +29,7 @@ function VendorsTransactionsHeader({ //#withVendorsTransactionsActions toggleVendorsTransactionsFilterDrawer: toggleFilterDrawer, }) { - const { formatMessage } = useIntl(); + // Filter form initial values. const initialValues = { @@ -41,11 +42,11 @@ function VendorsTransactionsHeader({ const validationSchema = Yup.object().shape({ fromDate: Yup.date() .required() - .label(formatMessage({ id: 'fromDate' })), + .label(intl.get('fromDate')), toDate: Yup.date() .min(Yup.ref('fromDate')) .required() - .label(formatMessage({ id: 'toDate' })), + .label(intl.get('toDate')), }); // Handle form submit. diff --git a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js index 870705c98..98343c537 100644 --- a/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js +++ b/client/src/containers/FinancialStatements/VendorsTransactions/VendorsTransactionsTable.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import FinancialSheet from 'components/FinancialSheet'; @@ -17,7 +17,7 @@ export default function VendorsTransactionsTable({ // #ownProps companyName, }) { - const { formatMessage } = useIntl(); + const { vendorsTransactions: { tableRows }, @@ -39,7 +39,7 @@ export default function VendorsTransactionsTable({ { return React.useMemo( () => [ { - Header: formatMessage({ id: 'vendor_name' }), + Header: intl.get('vendor_name'), accessor: ({ cells }) => { return ( { // width: 240, }, { - Header: formatMessage({ id: 'account_name' }), + Header: intl.get('account_name'), accessor: 'cells[1].value', className: 'name', textOverview: true, width: 170, }, { - Header: formatMessage({ id: 'reference_type' }), + Header: intl.get('reference_type'), accessor: 'cells[2].value', textOverview: true, width: 120, }, { - Header: formatMessage({ id: 'transaction_type' }), + Header: intl.get('transaction_type'), accessor: 'cells[3].value', textOverview: true, width: 120, }, { - Header: formatMessage({ id: 'credit' }), + Header: intl.get('credit'), accessor: 'cells[4].value', className: 'credit', textOverview: true, @@ -62,7 +62,7 @@ export const useVendorsTransactionsColumns = () => { }), }, { - Header: formatMessage({ id: 'debit' }), + Header: intl.get('debit'), accessor: 'cells[5].value', className: 'debit', textOverview: true, @@ -72,7 +72,7 @@ export const useVendorsTransactionsColumns = () => { }), }, { - Header: formatMessage({ id: 'running_balance' }), + Header: intl.get('running_balance'), accessor: 'cells[6].value', className: 'running_balance', textOverview: true, @@ -82,7 +82,7 @@ export const useVendorsTransactionsColumns = () => { }), }, ], - [tableRows, formatMessage], + [tableRows], ); }; diff --git a/client/src/containers/FinancialStatements/common.js b/client/src/containers/FinancialStatements/common.js index 73c7a3537..d9234a63d 100644 --- a/client/src/containers/FinancialStatements/common.js +++ b/client/src/containers/FinancialStatements/common.js @@ -1,44 +1,67 @@ import { omit } from 'lodash'; import { transfromToSnakeCase, flatObject } from 'utils'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const displayColumnsByOptions = [ - { key: 'total', name: 'Total', type: 'total', by: '' }, - { key: 'year', name: 'Date/Year', type: 'date_periods', by: 'year' }, - { key: 'month', name: 'Date/Month', type: 'date_periods', by: 'month' }, - { key: 'week', name: 'Date/Week', type: 'date_periods', by: 'month' }, - { key: 'day', name: 'Date/Day', type: 'date_periods', by: 'day' }, - { key: 'quarter', name: 'Date/Quarter', type: 'date_periods', by: 'quarter' }, + { key: 'total', name: intl.get('total'), type: 'total', by: '' }, + { + key: 'year', + name: intl.get('date_year'), + type: 'date_periods', + by: 'year', + }, + { + key: 'month', + name: intl.get('date_month'), + type: 'date_periods', + by: 'month', + }, + { + key: 'week', + name: intl.get('date_week'), + type: 'date_periods', + by: 'month', + }, + { + key: 'day', + name: intl.get('date_day'), + type: 'date_periods', + by: 'day', + }, + { + key: 'quarter', + name: intl.get('date_quarter'), + type: 'date_periods', + by: 'quarter', + }, ]; export const dateRangeOptions = [ - { value: 'today', label: 'Today' }, - { value: 'this_week', label: 'This Week' }, - { value: 'this_month', label: 'This Month' }, - { value: 'this_quarter', label: 'This Quarter' }, - { value: 'this_year', label: 'This Year' }, - { value: 'custom', label: 'Custom Range' }, + { value: 'today', label: intl.get('today') }, + { value: 'this_week', label: intl.get('this_week') }, + { value: 'this_month', label: intl.get('this_month') }, + { value: 'this_quarter', label: intl.get('this_quarter') }, + { value: 'this_year', label: intl.get('this_year') }, + { value: 'custom', label: intl.get('custom_range') }, ]; export const filterAccountsOptions = [ { key: 'all-accounts', - name: formatMessage({ id: 'all_accounts' }), - hint: formatMessage({ id: 'all_accounts_including_with_zero_balance' }), + name: intl.get('all_accounts'), + hint: intl.get('all_accounts_including_with_zero_balance'), }, { key: 'without-zero-balance', - name: formatMessage({ id: 'accounts_without_zero_balance' }), - hint: formatMessage({ - id: 'include_accounts_and_exclude_zero_balance', - }), + name: intl.get('accounts_without_zero_balance'), + hint: intl.get('include_accounts_and_exclude_zero_balance'), }, { key: 'with-transactions', - name: formatMessage({ id: 'accounts_with_transactions' }), - hint: formatMessage({ - id: 'include_accounts_once_has_transactions_on_given_date_period', - }), + name: intl.get('accounts_with_transactions'), + hint: intl.get( + 'include_accounts_once_has_transactions_on_given_date_period', + ), }, ]; diff --git a/client/src/containers/FinancialStatements/reducers.js b/client/src/containers/FinancialStatements/reducers.js index add2c4f49..7d6770764 100644 --- a/client/src/containers/FinancialStatements/reducers.js +++ b/client/src/containers/FinancialStatements/reducers.js @@ -1,5 +1,8 @@ +import React from 'react'; import { chain } from 'lodash'; import moment from 'moment'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; export const balanceSheetRowsReducer = (accounts) => { return accounts.map((account) => { @@ -10,7 +13,7 @@ export const balanceSheetRowsReducer = (accounts) => { ...(account.total && account.children && account.children.length > 0 ? [ { - name: `Total ${account.name}`, + name: intl.get('total_name', { name: account.name }), row_types: ['total-row', account.section_type], total: { ...account.total }, ...(account.total_periods && { @@ -46,12 +49,12 @@ export const profitLossSheetReducer = (profitLoss) => { if (profitLoss.income) { results.push({ - name: 'Income', + name: , total: profitLoss.income.total, children: [ ...profitLoss.income.accounts, { - name: 'Total Income', + name: , total: profitLoss.income.total, total_periods: profitLoss.income.total_periods, rowTypes: ['income_total', 'section_total', 'total'], @@ -62,12 +65,12 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.cost_of_sales) { results.push({ - name: 'Cost of sales', + name: , total: profitLoss.cost_of_sales.total, children: [ ...profitLoss.cost_of_sales.accounts, { - name: 'Total cost of sales', + name: , total: profitLoss.cost_of_sales.total, total_periods: profitLoss.cost_of_sales.total_periods, rowTypes: ['cogs_total', 'section_total', 'total'], @@ -78,7 +81,7 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.gross_profit) { results.push({ - name: 'Gross profit', + name: , total: profitLoss.gross_profit.total, total_periods: profitLoss.gross_profit.total_periods, rowTypes: ['gross_total', 'section_total', 'total'], @@ -86,12 +89,12 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.expenses) { results.push({ - name: 'Expenses', + name: , total: profitLoss.expenses.total, children: [ ...profitLoss.expenses.accounts, { - name: 'Total Expenses', + name: , total: profitLoss.expenses.total, total_periods: profitLoss.expenses.total_periods, rowTypes: ['expenses_total', 'section_total', 'total'], @@ -102,7 +105,7 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.operating_profit) { results.push({ - name: 'Net Operating income', + name: , total: profitLoss.operating_profit.total, total_periods: profitLoss.income.total_periods, rowTypes: ['net_operating_total', 'section_total', 'total'], @@ -110,13 +113,13 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.other_income) { results.push({ - name: 'Other Income', + name: 'other_income', total: profitLoss.other_income.total, total_periods: profitLoss.other_income.total_periods, children: [ ...profitLoss.other_income.accounts, { - name: 'Total other income', + name: , total: profitLoss.other_income.total, total_periods: profitLoss.other_income.total_periods, rowTypes: ['expenses_total', 'section_total', 'total'], @@ -126,13 +129,13 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.other_expenses) { results.push({ - name: 'Other expenses', + name: , total: profitLoss.other_expenses.total, total_periods: profitLoss.other_expenses.total_periods, children: [ ...profitLoss.other_expenses.accounts, { - name: 'Total other expenses', + name: , total: profitLoss.other_expenses.total, total_periods: profitLoss.other_expenses.total_periods, rowTypes: ['expenses_total', 'section_total', 'total'], @@ -142,7 +145,7 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.net_other_income) { results.push({ - name: 'Net other income', + name: , total: profitLoss.net_other_income.total, total_periods: profitLoss.net_other_income.total_periods, rowTypes: ['net_other_income', 'section_total', 'total'], @@ -150,7 +153,7 @@ export const profitLossSheetReducer = (profitLoss) => { } if (profitLoss.net_income) { results.push({ - name: 'Net Income', + name: , total: profitLoss.net_income.total, total_periods: profitLoss.net_income.total_periods, rowTypes: ['net_income_total', 'section_total', 'total'], @@ -215,7 +218,7 @@ export const generalLedgerTableRowsReducer = (accounts) => { children: [ { ...account.opening_balance, - name: 'Opening balance', + name: , rowType: 'OPENING_BALANCE', date: moment(account.opening_balance.date).format('DD MMM YYYY'), }, @@ -227,7 +230,7 @@ export const generalLedgerTableRowsReducer = (accounts) => { })), { ...account.closing_balance, - name: 'Closing balance', + name: , rowType: 'CLOSING_BALANCE', date: moment(account.closing_balance.date).format('DD MMM YYYY'), }, @@ -357,4 +360,4 @@ export const salesByItemsReducer = (sheet) => { }); } return results; -}; \ No newline at end of file +}; diff --git a/client/src/containers/GeneralSearch/Search.js b/client/src/containers/GeneralSearch/Search.js index a1d856b70..10c26a463 100644 --- a/client/src/containers/GeneralSearch/Search.js +++ b/client/src/containers/GeneralSearch/Search.js @@ -1,6 +1,7 @@ import React from 'react'; import { Omnibar } from '@blueprintjs/select'; import { MenuItem } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'components'; import { compose } from 'utils'; import withSearch from 'containers/GeneralSearch/withSearch'; @@ -30,7 +31,7 @@ function Search({ } + noResults={} />} onClose={() => closeGlobalSearch(false)} resetOnSelect={true} itemRenderer={renderSearch} diff --git a/client/src/containers/GlobalErrors/GlobalErrors.js b/client/src/containers/GlobalErrors/GlobalErrors.js index 24f3606c7..6cdc1bf9f 100644 --- a/client/src/containers/GlobalErrors/GlobalErrors.js +++ b/client/src/containers/GlobalErrors/GlobalErrors.js @@ -1,5 +1,5 @@ import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import AppToaster from 'components/AppToaster'; import withGlobalErrors from './withGlobalErrors'; @@ -16,11 +16,9 @@ function GlobalErrors({ // #withGlobalErrorsActions globalErrorsSet, }) { - const { formatMessage } = useIntl(); - if (globalErrors.something_wrong) { toastKeySessionExpired = AppToaster.show({ - message: formatMessage({ id: 'ops_something_went_wrong' }), + message: intl.get('ops_something_went_wrong'), intent: Intent.DANGER, onDismiss: () => { globalErrorsSet({ something_wrong: false }); @@ -30,7 +28,7 @@ function GlobalErrors({ if (globalErrors.session_expired) { toastKeySomethingWrong = AppToaster.show({ - message: formatMessage({ id: 'session_expired' }), + message: intl.get('session_expired'), intent: Intent.DANGER, onDismiss: () => { globalErrorsSet({ session_expired: false }); diff --git a/client/src/containers/Homepage/AnnouncementList.js b/client/src/containers/Homepage/AnnouncementList.js index 02ce51a4a..1f3d811fa 100644 --- a/client/src/containers/Homepage/AnnouncementList.js +++ b/client/src/containers/Homepage/AnnouncementList.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { announcementLists } from 'common/homepageOptions'; diff --git a/client/src/containers/InventoryAdjustments/InventoryAdjustmentTable.js b/client/src/containers/InventoryAdjustments/InventoryAdjustmentTable.js index 24f9416d7..52cb0a08a 100644 --- a/client/src/containers/InventoryAdjustments/InventoryAdjustmentTable.js +++ b/client/src/containers/InventoryAdjustments/InventoryAdjustmentTable.js @@ -1,6 +1,7 @@ import React, { useCallback } from 'react'; import { DataTable } from 'components'; import { useInventoryAdjustmentsColumns, ActionsMenu } from './components'; +import intl from 'react-intl-universal'; import withAlertsActions from 'containers/Alert/withAlertActions'; import withInventoryAdjustmentActions from './withInventoryAdjustmentActions'; @@ -23,14 +24,14 @@ function InventoryAdjustmentDataTable({ openAlert, // #ownProps - tableProps + tableProps, }) { const { isAdjustmentsLoading, isAdjustmentsFetching, inventoryAdjustments, - pagination + pagination, } = useInventoryAdjustmentsContext(); // Handle delete inventory adjustment transaction. @@ -47,40 +48,33 @@ function InventoryAdjustmentDataTable({ setInventoryAdjustmentTableState({ pageSize, pageIndex, - sortBy - }) + sortBy, + }); }, [setInventoryAdjustmentTableState], ); - return ( + return ( ); diff --git a/client/src/containers/InventoryAdjustments/components.js b/client/src/containers/InventoryAdjustments/components.js index 17aaac7a6..c21514ce0 100644 --- a/client/src/containers/InventoryAdjustments/components.js +++ b/client/src/containers/InventoryAdjustments/components.js @@ -9,9 +9,10 @@ import { Button, Popover, } from '@blueprintjs/core'; -import { useIntl, FormattedMessage as T } from 'react-intl'; +import intl from 'react-intl-universal'; import moment from 'moment'; -import { formatMessage } from 'services/intl'; + +import { FormattedMessage as T } from 'components'; import { isNumber } from 'lodash'; import { Icon, Money, If } from 'components'; import { isBlank, safeCallback } from 'utils'; @@ -37,7 +38,7 @@ export const PublishAccessor = (r) => { export const TypeAccessor = (row) => { return row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : ( '' @@ -50,7 +51,7 @@ export const TypeAccessor = (row) => { export const ItemCodeAccessor = (row) => row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : ( '' @@ -85,7 +86,7 @@ export const SellPriceCell = ({ cell: { value } }) => { export const ItemTypeAccessor = (row) => { return row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : null; }; @@ -98,11 +99,11 @@ export const ActionsMenu = ({ } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } @@ -125,48 +126,48 @@ export const ActionsCell = (props) => { * Retrieve inventory adjustments columns. */ export const useInventoryAdjustmentsColumns = () => { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'date', - Header: formatMessage({ id: 'date' }), + Header: intl.get('date'), accessor: (r) => moment(r.date).format('YYYY MMM DD'), width: 115, className: 'date', }, { id: 'type', - Header: formatMessage({ id: 'type' }), + Header: intl.get('type'), accessor: TypeAccessor, className: 'type', width: 100, }, { id: 'reason', - Header: formatMessage({ id: 'reason' }), + Header: intl.get('reason'), accessor: 'reason', className: 'reason', width: 115, }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference_no', className: 'reference_no', width: 100, }, { id: 'published_at', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: PublishAccessor, width: 95, className: 'publish', }, { id: 'description', - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', disableSorting: true, width: 85, @@ -174,12 +175,12 @@ export const useInventoryAdjustmentsColumns = () => { }, { id: 'created_at', - Header: formatMessage({ id: 'created_at' }), + Header: intl.get('created_at'), accessor: (r) => moment(r.created_at).format('YYYY MMM DD'), width: 125, className: 'created_at', }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index 91e74a14d..8a59dd093 100644 --- a/client/src/containers/Items/ItemForm.js +++ b/client/src/containers/Items/ItemForm.js @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { defaultTo } from 'lodash'; @@ -66,8 +66,6 @@ function ItemForm({ // History context. const history = useHistory(); - const { formatMessage } = useIntl(); - /** * Initial values in create and edit mode. */ @@ -105,13 +103,11 @@ function ItemForm({ const fields = {}; if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) { - fields.name = formatMessage({ id: 'the_name_used_before' }); + fields.name = intl.get('the_name_used_before'); } if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) { AppToaster.show({ - message: formatMessage({ - id: 'cannot_change_item_inventory_account', - }), + message: intl.get('cannot_change_item_inventory_account'), intent: Intent.DANGER, }); } @@ -128,12 +124,10 @@ function ItemForm({ const onSuccess = (response) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_item_has_been_created_successfully' - : 'the_item_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_item_has_been_created_successfully' + : 'the_item_has_been_edited_successfully', { number: itemId, }, diff --git a/client/src/containers/Items/ItemForm.schema.js b/client/src/containers/Items/ItemForm.schema.js index 283934c4f..cc1a59c3d 100644 --- a/client/src/containers/Items/ItemForm.schema.js +++ b/client/src/containers/Items/ItemForm.schema.js @@ -1,6 +1,6 @@ import * as Yup from 'yup'; import { defaultTo } from 'lodash'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ @@ -9,13 +9,13 @@ const Schema = Yup.object().shape({ .required() .min(0) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'item_name_' })), + .label(intl.get('item_name_')), type: Yup.string() .trim() .required() .min(0) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'item_type_' })), + .label(intl.get('item_type_')), code: Yup.string().trim().min(0).max(DATATYPES_LENGTH.STRING), cost_price: Yup.number() .min(0) @@ -24,7 +24,7 @@ const Schema = Yup.object().shape({ is: true, then: Yup.number() .required() - .label(formatMessage({ id: 'cost_price_' })), + .label(intl.get('cost_price_')), otherwise: Yup.number().nullable(true), }), sell_price: Yup.number() @@ -34,7 +34,7 @@ const Schema = Yup.object().shape({ is: true, then: Yup.number() .required() - .label(formatMessage({ id: 'sell_price_' })), + .label(intl.get('sell_price_')), otherwise: Yup.number().nullable(true), }), cost_account_id: Yup.number() @@ -43,21 +43,21 @@ const Schema = Yup.object().shape({ then: Yup.number().required(), otherwise: Yup.number().nullable(true), }) - .label(formatMessage({ id: 'cost_account_id' })), + .label(intl.get('cost_account_id')), sell_account_id: Yup.number() .when(['sellable'], { is: true, then: Yup.number().required(), otherwise: Yup.number().nullable(), }) - .label(formatMessage({ id: 'sell_account_id' })), + .label(intl.get('sell_account_id')), inventory_account_id: Yup.number() .when(['type'], { is: (value) => value === 'inventory', then: Yup.number().required(), otherwise: Yup.number().nullable(), }) - .label(formatMessage({ id: 'inventory_account' })), + .label(intl.get('inventory_account')), category_id: Yup.number().positive().nullable(), stock: Yup.string() || Yup.boolean(), sellable: Yup.boolean().required(), diff --git a/client/src/containers/Items/ItemFormBody.js b/client/src/containers/Items/ItemFormBody.js index 5255e1381..9123ebf4d 100644 --- a/client/src/containers/Items/ItemFormBody.js +++ b/client/src/containers/Items/ItemFormBody.js @@ -15,7 +15,7 @@ import { Hint, InputPrependText, } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { useItemFormContext } from './ItemFormProvider'; diff --git a/client/src/containers/Items/ItemFormFloatingActions.js b/client/src/containers/Items/ItemFormFloatingActions.js index 0fd64ea46..eacce65db 100644 --- a/client/src/containers/Items/ItemFormFloatingActions.js +++ b/client/src/containers/Items/ItemFormFloatingActions.js @@ -1,6 +1,6 @@ import React from 'react'; import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import classNames from 'classnames'; import { FastField, useFormikContext } from 'formik'; diff --git a/client/src/containers/Items/ItemFormInventorySection.js b/client/src/containers/Items/ItemFormInventorySection.js index 79a440c95..c4684fcec 100644 --- a/client/src/containers/Items/ItemFormInventorySection.js +++ b/client/src/containers/Items/ItemFormInventorySection.js @@ -3,7 +3,7 @@ import { FastField, ErrorMessage } from 'formik'; import { FormGroup } from '@blueprintjs/core'; import { AccountsSelectList, Col, Row } from 'components'; import { CLASSES } from 'common/classes'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import withSettings from 'containers/Settings/withSettings'; diff --git a/client/src/containers/Items/ItemFormPrimarySection.js b/client/src/containers/Items/ItemFormPrimarySection.js index 8c8ff9954..db6ae61d8 100644 --- a/client/src/containers/Items/ItemFormPrimarySection.js +++ b/client/src/containers/Items/ItemFormPrimarySection.js @@ -7,7 +7,7 @@ import { Radio, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import { ErrorMessage, FastField } from 'formik'; import { CategoriesSelectList, @@ -41,18 +41,15 @@ export default function ItemFormPrimarySection() { const itemTypeHintContent = ( <>
- {'Service: '} - {'Services that you provide to customers. '} +
- {'Inventory: '} - {'Products you buy and/or sell and that you track quantities of.'} +
- {'Non-Inventory: '} - { - 'Products you buy and/or sell but don’t need to (or can’t) track quantities of, for example, nuts and bolts used in an installation.' - } +
); diff --git a/client/src/containers/Items/ItemFormProvider.js b/client/src/containers/Items/ItemFormProvider.js index 94a9fcf0d..ceab6f736 100644 --- a/client/src/containers/Items/ItemFormProvider.js +++ b/client/src/containers/Items/ItemFormProvider.js @@ -1,5 +1,5 @@ import React, { useEffect, createContext, useState } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { useLocation, useParams } from 'react-router-dom'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { @@ -66,7 +66,7 @@ function ItemFormProvider({ itemId, ...props }) { }; // Format message intl. - const { formatMessage } = useIntl(); + // Change page title dispatcher. const changePageTitle = useDashboardPageTitle(); @@ -74,9 +74,9 @@ function ItemFormProvider({ itemId, ...props }) { // Changes the page title in new and edit mode. useEffect(() => { isNewMode - ? changePageTitle(formatMessage({ id: 'new_item' })) - : changePageTitle(formatMessage({ id: 'edit_item_details' })); - }, [changePageTitle, isNewMode, formatMessage]); + ? changePageTitle(intl.get('new_item')) + : changePageTitle(intl.get('edit_item_details')); + }, [changePageTitle, isNewMode]); return ( } diff --git a/client/src/containers/Items/ItemsFooter.js b/client/src/containers/Items/ItemsFooter.js index 57b0e9386..1404143f9 100644 --- a/client/src/containers/Items/ItemsFooter.js +++ b/client/src/containers/Items/ItemsFooter.js @@ -1,6 +1,6 @@ import React from 'react'; import { Intent, Button } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default function ItemFloatingFooter({ formik: { isSubmitting }, diff --git a/client/src/containers/Items/components.js b/client/src/containers/Items/components.js index a44a50ad3..14d5d897e 100644 --- a/client/src/containers/Items/components.js +++ b/client/src/containers/Items/components.js @@ -9,8 +9,8 @@ import { Button, Popover, } from '@blueprintjs/core'; -import { useIntl, FormattedMessage as T } from 'react-intl'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T } from 'components'; import { isNumber } from 'lodash'; import { Icon, Money, If } from 'components'; import { isBlank, safeCallback } from 'utils'; @@ -33,7 +33,7 @@ export const PublishAccessor = (r) => { export const TypeAccessor = (row) => { return row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : ( '' @@ -43,7 +43,7 @@ export const TypeAccessor = (row) => { export const ItemCodeAccessor = (row) => row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : ( '' @@ -66,7 +66,7 @@ export const SellPriceCell = ({ cell: { value } }) => { export const ItemTypeAccessor = (row) => { return row.type ? ( - {formatMessage({ id: row.type })} + {intl.get(row.type)} ) : null; }; @@ -82,7 +82,7 @@ export function ItemsActionMenuList({ onDuplicate, }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'edit_item' })} + text={intl.get('edit_item')} onClick={safeCallback(onEditItem, original)} /> } - text={formatMessage({ id: 'duplicate' })} + text={intl.get('duplicate')} onClick={safeCallback(onDuplicate, original)} /> } onClick={safeCallback(onInactivateItem, original)} /> } onClick={safeCallback(onActivateItem, original)} /> } onClick={safeCallback(onMakeAdjustment, original)} /> } onClick={safeCallback(onDeleteItem, original)} intent={Intent.DANGER} @@ -146,60 +146,60 @@ export const ItemsActionsTableCell = (props) => { * Retrieve all items table columns. */ export const useItemsTableColumns = () => { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'name', - Header: formatMessage({ id: 'item_name' }), + Header: intl.get('item_name'), accessor: 'name', className: 'name', width: 180, }, { id: 'code', - Header: formatMessage({ id: 'item_code' }), + Header: intl.get('item_code'), accessor: 'code', className: 'code', width: 120, }, { id: 'type', - Header: formatMessage({ id: 'item_type' }), + Header: intl.get('item_type'), accessor: ItemTypeAccessor, className: 'item_type', width: 120, }, { id: 'category', - Header: formatMessage({ id: 'category' }), + Header: intl.get('category'), accessor: 'category.name', className: 'category', width: 150, }, { id: 'sell_price', - Header: formatMessage({ id: 'sell_price' }), + Header: intl.get('sell_price'), accessor: 'sell_price_formatted', className: 'sell-price', width: 150, }, { id: 'cost_price', - Header: formatMessage({ id: 'cost_price' }), + Header: intl.get('cost_price'), accessor: 'cost_price_formatted', className: 'cost-price', width: 150, }, { id: 'quantity_on_hand', - Header: formatMessage({ id: 'quantity_on_hand' }), + Header: intl.get('quantity_on_hand'), accessor: 'quantity_on_hand', Cell: QuantityOnHandCell, width: 140, }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/Items/utils.js b/client/src/containers/Items/utils.js index 56d9f7b9b..1a5abe31d 100644 --- a/client/src/containers/Items/utils.js +++ b/client/src/containers/Items/utils.js @@ -1,12 +1,12 @@ -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; export const transitionItemTypeKeyToLabel = (itemTypeKey) => { const table = { - service: formatMessage({ id: 'service' }), - inventory: formatMessage({ id: 'inventory' }), - 'non-inventory': formatMessage({ id: 'non_inventory' }), + service: intl.get('service'), + inventory: intl.get('inventory'), + 'non-inventory': intl.get('non_inventory'), }; return typeof table[itemTypeKey] === 'string' ? table[itemTypeKey] : ''; }; @@ -17,9 +17,7 @@ export const handleDeleteErrors = (errors) => { errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTINS') ) { AppToaster.show({ - message: formatMessage({ - id: 'the_item_has_associated_transactions', - }), + message: intl.get('the_item_has_associated_transactions'), intent: Intent.DANGER, }); } @@ -30,10 +28,7 @@ export const handleDeleteErrors = (errors) => { ) ) { AppToaster.show({ - message: formatMessage({ - id: - 'you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions', - }), + message: intl.get('you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions'), intent: Intent.DANGER, }); } @@ -43,10 +38,7 @@ export const handleDeleteErrors = (errors) => { ) ) { AppToaster.show({ - message: formatMessage({ - id: - 'cannot_change_item_type_to_inventory_with_item_has_associated_transactions', - }), + message: intl.get('cannot_change_item_type_to_inventory_with_item_has_associated_transactions'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/ItemsCategories/ItemCategoriesTable.js b/client/src/containers/ItemsCategories/ItemCategoriesTable.js index c2e515f87..5f2436e91 100644 --- a/client/src/containers/ItemsCategories/ItemCategoriesTable.js +++ b/client/src/containers/ItemsCategories/ItemCategoriesTable.js @@ -1,4 +1,5 @@ import React from 'react'; +import intl from 'react-intl-universal'; import { useItemsCategoriesTableColumns, ActionMenuList } from './components'; import DataTable from 'components/DataTable'; @@ -25,11 +26,8 @@ function ItemsCategoryTable({ openAlert, }) { // Items categories context. - const { - isCategoriesLoading, - isCategoriesFetching, - itemsCategories, - } = useItemsCategoriesContext(); + const { isCategoriesLoading, isCategoriesFetching, itemsCategories } = + useItemsCategoriesContext(); // Table columns. const columns = useItemsCategoriesTableColumns(); @@ -56,7 +54,7 @@ function ItemsCategoryTable({ sticky={true} selectionColumn={true} TableLoadingRenderer={TableSkeletonRows} - noResults={'There is no items categories in table yet.'} + noResults={intl.get('there_is_no_items_categories_in_table_yet')} payload={{ onDeleteCategory: handleDeleteCategory, onEditCategory: handleEditCategory, diff --git a/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js b/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js index 4acb452c1..e47942012 100644 --- a/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js +++ b/client/src/containers/ItemsCategories/ItemsCategoryActionsBar.js @@ -9,7 +9,7 @@ import { Position, PopoverInteractionKind, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { If, Icon } from 'components'; diff --git a/client/src/containers/ItemsCategories/components.js b/client/src/containers/ItemsCategories/components.js index d3997c198..bae6681c6 100644 --- a/client/src/containers/ItemsCategories/components.js +++ b/client/src/containers/ItemsCategories/components.js @@ -8,7 +8,7 @@ import { MenuDivider, Intent, } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Icon } from 'components'; import { safeCallback } from 'utils'; @@ -19,18 +19,18 @@ export function ActionMenuList({ row: { original }, payload: { onEditCategory, onDeleteCategory }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'edit_category' })} + text={intl.get('edit_category')} onClick={safeCallback(onEditCategory, original)} /> } @@ -57,32 +57,32 @@ export function TableActionsCell(props) { * Retrieve the items categories table columns. */ export function useItemsCategoriesTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'name', - Header: formatMessage({ id: 'category_name' }), + Header: intl.get('category_name'), accessor: 'name', width: 220, }, { id: 'count', - Header: formatMessage({ id: 'count' }), + Header: intl.get('count'), accessor: 'count', className: 'count', width: 180, }, { id: 'description', - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', className: 'description', width: 220, } ], - [formatMessage], + [], ); } \ No newline at end of file diff --git a/client/src/containers/JournalNumber/ReferenceNumberForm.js b/client/src/containers/JournalNumber/ReferenceNumberForm.js index bece2a4c1..515061537 100644 --- a/client/src/containers/JournalNumber/ReferenceNumberForm.js +++ b/client/src/containers/JournalNumber/ReferenceNumberForm.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import * as Yup from 'yup'; import { Formik, Form } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Button, Classes } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core'; diff --git a/client/src/containers/JournalNumber/ReferenceNumberFormContent.js b/client/src/containers/JournalNumber/ReferenceNumberFormContent.js index 8988c7b2b..84d18562e 100644 --- a/client/src/containers/JournalNumber/ReferenceNumberFormContent.js +++ b/client/src/containers/JournalNumber/ReferenceNumberFormContent.js @@ -1,6 +1,6 @@ import React from 'react'; import { FastField, useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FormGroup, InputGroup, Radio } from '@blueprintjs/core'; import { If, Row, Col, ErrorMessage } from 'components'; import { inputIntent } from 'utils'; @@ -73,7 +73,7 @@ export default function ReferenceNumberFormContent() { {({ form, field, meta: { error, touched } }) => ( } value="manual" onChange={() => { form.setFieldValue('incrementMode', 'manual'); @@ -88,7 +88,7 @@ export default function ReferenceNumberFormContent() { {({ form, field, meta: { error, touched } }) => ( } value="manual" onChange={() => { form.setFieldValue('incrementMode', 'manual-transaction'); diff --git a/client/src/containers/KeyboardShortcuts/ShortcutsTable.js b/client/src/containers/KeyboardShortcuts/ShortcutsTable.js index ab8a5dce4..da5974556 100644 --- a/client/src/containers/KeyboardShortcuts/ShortcutsTable.js +++ b/client/src/containers/KeyboardShortcuts/ShortcutsTable.js @@ -1,18 +1,19 @@ import React, { useMemo } from 'react'; import { DataTable } from 'components'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import keyboardShortcuts from 'common/keyboardShortcutsOptions'; /** * keyboard shortcuts table. */ function ShortcutsTable() { - const { formatMessage } = useIntl(); + const columns = useMemo( () => [ { - Header: formatMessage({ id: 'shortcut_keys' }), + Header: intl.get('shortcut_keys'), accessor: 'shortcut_key', disableSortBy: true, className: 'shortcut_key', @@ -20,14 +21,14 @@ function ShortcutsTable() { }, { id: 'description', - Header: formatMessage({ id: 'description' }), + Header: intl.get('description'), accessor: 'description', disableSortBy: true, className: 'description', width: 250, }, ], - [formatMessage], + [], ); return ; diff --git a/client/src/containers/Preferences/Accountant/AccountantForm.js b/client/src/containers/Preferences/Accountant/AccountantForm.js index 3a7c86a08..1cb7333bd 100644 --- a/client/src/containers/Preferences/Accountant/AccountantForm.js +++ b/client/src/containers/Preferences/Accountant/AccountantForm.js @@ -9,8 +9,13 @@ import { Intent, } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; -import { AccountsSelectList, FieldRequiredHint } from 'components'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; + +import { + FormattedMessage as T, + AccountsSelectList, + FieldRequiredHint, +} from 'components'; import { handleStringChange, inputIntent } from 'utils'; import { useAccountantFormContext } from './AccountantFormProvider'; @@ -21,8 +26,6 @@ import { useAccountantFormContext } from './AccountantFormProvider'; export default function AccountantForm() { const history = useHistory(); - const { formatMessage } = useIntl(); - const { isSubmitting } = useFormikContext(); const handleCloseClick = () => { @@ -48,7 +51,11 @@ export default function AccountantForm() { + } name={'account_code_required'} {...field} /> @@ -62,7 +69,11 @@ export default function AccountantForm() { } name={'account_code_unique'} {...field} @@ -94,8 +105,8 @@ export default function AccountantForm() { setFieldValue('accounting_basis', _value); })} > - - + + )} @@ -115,7 +126,11 @@ export default function AccountantForm() { } helperText={ - 'Select a preferred account to deposit into it after customer make payment.' + } labelInfo={} intent={inputIntent({ error, touched })} @@ -147,7 +162,11 @@ export default function AccountantForm() { } helperText={ - 'Select a preferred account to deposit into it after customer make payment.' + } labelInfo={} intent={inputIntent({ error, touched })} @@ -178,7 +197,11 @@ export default function AccountantForm() { } helperText={ - 'Select a preferred account to deposit into it vendor advanced deposits.' + } labelInfo={} intent={inputIntent({ error, touched })} diff --git a/client/src/containers/Preferences/Accountant/AccountantFormPage.js b/client/src/containers/Preferences/Accountant/AccountantFormPage.js index 72d143b37..33d259f84 100644 --- a/client/src/containers/Preferences/Accountant/AccountantFormPage.js +++ b/client/src/containers/Preferences/Accountant/AccountantFormPage.js @@ -4,7 +4,7 @@ import { pick } from 'lodash'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withSettings from 'containers/Settings/withSettings'; @@ -28,7 +28,7 @@ function AccountantFormPage({ accountsSettings, billPaymentSettings, }) { - const { formatMessage } = useIntl(); + const { saveSettingMutate } = useAccountantFormContext(); @@ -44,7 +44,7 @@ function AccountantFormPage({ }; useEffect(() => { - changePreferencesPageTitle(formatMessage({ id: 'accountant' })); + changePreferencesPageTitle(intl.get('accountant')); }, [changePreferencesPageTitle]); const handleFormSubmit = (values, { setSubmitting }) => { @@ -53,9 +53,7 @@ function AccountantFormPage({ setSubmitting(true); const onSuccess = () => { AppToaster.show({ - message: formatMessage({ - id: 'the_accountant_preferences_has_been_saved', - }), + message: intl.get('the_accountant_preferences_has_been_saved'), intent: Intent.SUCCESS, }); setSubmitting(false); diff --git a/client/src/containers/Preferences/Accounts/Accounts.js b/client/src/containers/Preferences/Accounts/Accounts.js index 478141122..bb118e9c3 100644 --- a/client/src/containers/Preferences/Accounts/Accounts.js +++ b/client/src/containers/Preferences/Accounts/Accounts.js @@ -2,7 +2,7 @@ import React from 'react'; import {Tabs, Tab} from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import PreferencesSubContent from 'components/Preferences/PreferencesSubContent'; -import { FormattedMessage as T} from 'react-intl'; +import { FormattedMessage as T } from 'components'; export default function AccountsPreferences() { const history = useHistory(); diff --git a/client/src/containers/Preferences/Accounts/AccountsCustomFields.js b/client/src/containers/Preferences/Accounts/AccountsCustomFields.js index 16932fcc8..4ecfdca36 100644 --- a/client/src/containers/Preferences/Accounts/AccountsCustomFields.js +++ b/client/src/containers/Preferences/Accounts/AccountsCustomFields.js @@ -18,7 +18,8 @@ import {connect} from 'react-redux'; import { fetchResourceFields, } from 'store/customFields/customFields.actions'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; function AccountsCustomFields({ fetchResourceFields, fields }) { const fetchHook = useAsync(async () => { diff --git a/client/src/containers/Preferences/Currencies/CurrenciesActions.js b/client/src/containers/Preferences/Currencies/CurrenciesActions.js index 1e263dea3..fe0102bf4 100644 --- a/client/src/containers/Preferences/Currencies/CurrenciesActions.js +++ b/client/src/containers/Preferences/Currencies/CurrenciesActions.js @@ -1,15 +1,11 @@ -import React, {useCallback} from 'react'; -import { - Button, - Intent, -} from '@blueprintjs/core'; +import React, { useCallback } from 'react'; +import { Button, Intent } from '@blueprintjs/core'; import Icon from 'components/Icon'; import withDialogActions from 'containers/Dialog/withDialogActions'; -import {compose} from 'utils'; +import { compose } from 'utils'; +import { FormattedMessage as T } from 'components'; -function CurrenciesActions({ - openDialog, -}) { +function CurrenciesActions({ openDialog }) { const handleClickNewCurrency = useCallback(() => { openDialog('currency-form'); }, [openDialog]); @@ -17,15 +13,14 @@ function CurrenciesActions({ return (
-
+
); } -export default compose( - withDialogActions, -)(CurrenciesActions); \ No newline at end of file +export default compose(withDialogActions)(CurrenciesActions); diff --git a/client/src/containers/Preferences/Currencies/CurrenciesList.js b/client/src/containers/Preferences/Currencies/CurrenciesList.js index 72ac8833b..d6a9c820a 100644 --- a/client/src/containers/Preferences/Currencies/CurrenciesList.js +++ b/client/src/containers/Preferences/Currencies/CurrenciesList.js @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { CurrenciesProvider } from './CurrenciesProvider'; import CurrenciesDataTable from './CurrenciesDataTable'; @@ -14,11 +15,9 @@ function CurrenciesList({ // #withDashboardActions changePreferencesPageTitle, }) { - const { formatMessage } = useIntl(); - useEffect(() => { - changePreferencesPageTitle(formatMessage({ id: 'currencies' })); - }, [changePreferencesPageTitle, formatMessage]); + changePreferencesPageTitle(intl.get('currencies')); + }, [changePreferencesPageTitle]); return ( diff --git a/client/src/containers/Preferences/Currencies/components.js b/client/src/containers/Preferences/Currencies/components.js index 6a0a8a9c7..51eb2371c 100644 --- a/client/src/containers/Preferences/Currencies/components.js +++ b/client/src/containers/Preferences/Currencies/components.js @@ -7,7 +7,7 @@ import { MenuItem, Intent, } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Icon } from 'components'; import { safeCallback } from 'utils'; @@ -18,17 +18,17 @@ export function ActionMenuList({ row: { original }, payload: { onEditCurrency, onDeleteCurrency }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'edit_currency' })} + text={intl.get('edit_currency')} onClick={safeCallback(onEditCurrency, original)} /> } - text={formatMessage({ id: 'delete_currency' })} + text={intl.get('delete_currency')} onClick={safeCallback(onDeleteCurrency, original)} intent={Intent.DANGER} /> @@ -51,17 +51,17 @@ export const ActionsCell = (props) => { }; export function useCurrenciesTableColumns() { - const { formatMessage } = useIntl(); + return useMemo( () => [ { - Header: formatMessage({ id: 'currency_name' }), + Header: intl.get('currency_name'), accessor: 'currency_name', width: 150, }, { - Header: formatMessage({ id: 'currency_code' }), + Header: intl.get('currency_code'), accessor: 'currency_code', className: 'currency_code', width: 120, @@ -80,6 +80,6 @@ export function useCurrenciesTableColumns() { disableResizing: true, }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Preferences/General/General.schema.js b/client/src/containers/Preferences/General/General.schema.js index 48c893188..6ac713d1b 100644 --- a/client/src/containers/Preferences/General/General.schema.js +++ b/client/src/containers/Preferences/General/General.schema.js @@ -1,34 +1,34 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; const Schema = Yup.object().shape({ name: Yup.string() .required() - .label(formatMessage({ id: 'organization_name_' })), + .label(intl.get('organization_name_')), financial_date_start: Yup.date() .required() - .label(formatMessage({ id: 'date_start_' })), + .label(intl.get('date_start_')), industry: Yup.string() .nullable() - .label(formatMessage({ id: 'organization_industry_' })), + .label(intl.get('organization_industry_')), location: Yup.string() .nullable() - .label(formatMessage({ id: 'location' })), + .label(intl.get('location')), base_currency: Yup.string() .required() - .label(formatMessage({ id: 'base_currency_' })), + .label(intl.get('base_currency_')), fiscal_year: Yup.string() .required() - .label(formatMessage({ id: 'fiscal_year_' })), + .label(intl.get('fiscal_year_')), language: Yup.string() .required() - .label(formatMessage({ id: 'language' })), + .label(intl.get('language')), time_zone: Yup.string() .required() - .label(formatMessage({ id: 'time_zone_' })), + .label(intl.get('time_zone_')), date_format: Yup.string() .required() - .label(formatMessage({ id: 'date_format_' })), + .label(intl.get('date_format_')), }); export const PreferencesGeneralSchema = Schema; diff --git a/client/src/containers/Preferences/General/GeneralForm.js b/client/src/containers/Preferences/General/GeneralForm.js index 856a9a8e5..06c164b2a 100644 --- a/client/src/containers/Preferences/General/GeneralForm.js +++ b/client/src/containers/Preferences/General/GeneralForm.js @@ -10,9 +10,10 @@ import { import classNames from 'classnames'; import { TimezonePicker } from '@blueprintjs/timezone'; import { ErrorMessage, FastField } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; import { DateInput } from '@blueprintjs/datetime'; import { useHistory } from 'react-router-dom'; + +import { FormattedMessage as T } from 'components'; import { ListSelect, FieldRequiredHint } from 'components'; import { inputIntent, @@ -23,12 +24,13 @@ import { import { CLASSES } from 'common/classes'; import countriesOptions from 'common/countries'; import currencies from 'common/currencies'; -import fiscalYearOptions from 'common/fiscalYearOptions'; +import { getFiscalYearOptions } from 'common/fiscalYearOptions'; import languages from 'common/languagesOptions'; import dateFormatsOptions from 'common/dateFormatsOptions'; export default function PreferencesGeneralForm({}) { const history = useHistory(); + const fiscalYearOptions = getFiscalYearOptions(); const handleCloseClick = () => { history.go(-1); @@ -44,7 +46,7 @@ export default function PreferencesGeneralForm({}) { inline={true} intent={inputIntent({ error, touched })} className={'form-group--org-name'} - helperText={'Shown on sales forms and purchase orders.'} + helperText={} > @@ -59,9 +61,7 @@ export default function PreferencesGeneralForm({}) { inline={true} intent={inputIntent({ error, touched })} className={classNames('form-group--select-list', CLASSES.FILL)} - helperText={ - 'For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).' - } + helperText={} > } > { - changePreferencesPageTitle(formatMessage({ id: 'general' })); - }, [changePreferencesPageTitle, formatMessage]); + changePreferencesPageTitle(intl.get('general')); + }, [changePreferencesPageTitle]); function transformGeneralSettings(data) { return mapKeys(data, (value, key) => snakeCase(key)); diff --git a/client/src/containers/Preferences/Item/ItemForm.js b/client/src/containers/Preferences/Item/ItemForm.js index 5ac6cf265..525fec756 100644 --- a/client/src/containers/Preferences/Item/ItemForm.js +++ b/client/src/containers/Preferences/Item/ItemForm.js @@ -2,7 +2,8 @@ import React from 'react'; import { Form, FastField, useFormikContext } from 'formik'; import { FormGroup, Button, Intent } from '@blueprintjs/core'; import { AccountsSelectList, FieldRequiredHint } from 'components'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { useHistory } from 'react-router-dom'; import { inputIntent } from 'utils'; import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'common/accountTypes'; @@ -38,7 +39,11 @@ export default function ItemForm() { } helperText={ - 'Select a preferred account to deposit into it after customer make payment.' + } labelInfo={} intent={inputIntent({ error, touched })} @@ -70,7 +75,11 @@ export default function ItemForm() { } helperText={ - 'Select a preferred account to deposit into it after customer make payment.' + } labelInfo={} intent={inputIntent({ error, touched })} @@ -102,7 +111,11 @@ export default function ItemForm() { } helperText={ - 'Select a preferred account to deposit into it vendor advanced deposits.' + } labelInfo={} intent={inputIntent({ error, touched })} diff --git a/client/src/containers/Preferences/Item/ItemFormPage.js b/client/src/containers/Preferences/Item/ItemFormPage.js index 512d5d732..e7f37aa6d 100644 --- a/client/src/containers/Preferences/Item/ItemFormPage.js +++ b/client/src/containers/Preferences/Item/ItemFormPage.js @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { ItemPreferencesSchema } from './Item.schema'; import ItemForm from './ItemForm'; @@ -21,7 +21,7 @@ function ItemFormPage({ // #withDashboardActions changePreferencesPageTitle, }) { - const { formatMessage } = useIntl(); + const { saveSettingMutate } = useItemFormContext(); const initialValues = { @@ -32,8 +32,8 @@ function ItemFormPage({ }; useEffect(() => { - changePreferencesPageTitle(formatMessage({ id: 'items' })); - }, [formatMessage, changePreferencesPageTitle]); + changePreferencesPageTitle(intl.get('items')); + }, [changePreferencesPageTitle]); // Handle form submit. const handleFormSubmit = (values, { setSubmitting, setErrors }) => { @@ -42,9 +42,7 @@ function ItemFormPage({ const onSuccess = () => { AppToaster.show({ - message: formatMessage({ - id: 'the_items_preferences_has_been_saved', - }), + message: intl.get('the_items_preferences_has_been_saved'), intent: Intent.SUCCESS, }); setSubmitting(false); diff --git a/client/src/containers/Preferences/Users/Users.js b/client/src/containers/Preferences/Users/Users.js index 31ec33693..ed5e9a806 100644 --- a/client/src/containers/Preferences/Users/Users.js +++ b/client/src/containers/Preferences/Users/Users.js @@ -1,8 +1,9 @@ import React from 'react'; import { Tabs, Tab } from '@blueprintjs/core'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; -import 'style/pages/Preferences/Users.scss' +import 'style/pages/Preferences/Users.scss'; import { CLASSES } from 'common/classes'; import PreferencesSubContent from 'components/Preferences/PreferencesSubContent'; @@ -16,15 +17,17 @@ function UsersPreferences({ openDialog }) { const onChangeTabs = (currentTabId) => {}; return ( -
+
- - + +
diff --git a/client/src/containers/Preferences/Users/UsersActions.js b/client/src/containers/Preferences/Users/UsersActions.js index 9a5a5ef79..ada5c76af 100644 --- a/client/src/containers/Preferences/Users/UsersActions.js +++ b/client/src/containers/Preferences/Users/UsersActions.js @@ -3,7 +3,7 @@ import { Button, Intent, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import Icon from 'components/Icon'; import withDialogActions from 'containers/Dialog/withDialogActions'; diff --git a/client/src/containers/Preferences/Users/UsersList.js b/client/src/containers/Preferences/Users/UsersList.js index 840aaa0f6..986022925 100644 --- a/client/src/containers/Preferences/Users/UsersList.js +++ b/client/src/containers/Preferences/Users/UsersList.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import {UsersListProvider } from './UsersProvider'; import withDashboardActions from 'containers/Dashboard/withDashboardActions'; @@ -15,11 +15,10 @@ function UsersListPreferences({ // #withDashboardActions changePreferencesPageTitle, }) { - const { formatMessage } = useIntl(); useEffect(() => { - changePreferencesPageTitle(formatMessage({ id: 'users' })); - }, [changePreferencesPageTitle, formatMessage]); + changePreferencesPageTitle(intl.get('users')); + }, [changePreferencesPageTitle]); return ( diff --git a/client/src/containers/Preferences/Users/components.js b/client/src/containers/Preferences/Users/components.js index 0118c569f..6b288a0df 100644 --- a/client/src/containers/Preferences/Users/components.js +++ b/client/src/containers/Preferences/Users/components.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Intent, Button, @@ -27,27 +28,27 @@ export function ActionsMenu({ row: { original }, payload: { onEdit, onInactivate, onActivate, onDelete, onResendInvitation }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'edit_user' })} + text={intl.get('edit_user')} onClick={safeCallback(onEdit, original)} /> {original.active ? ( } /> ) : ( } /> @@ -64,7 +65,7 @@ export function ActionsMenu({ } - text={formatMessage({ id: 'delete_user' })} + text={intl.get('delete_user')} onClick={safeCallback(onDelete, original)} intent={Intent.DANGER} /> @@ -110,7 +111,7 @@ function FullNameAccessor(user) { } export const useUsersListColumns = () => { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ @@ -122,19 +123,19 @@ export const useUsersListColumns = () => { }, { id: 'full_name', - Header: formatMessage({ id: 'full_name' }), + Header: intl.get('full_name'), accessor: FullNameAccessor, width: 150, }, { id: 'email', - Header: formatMessage({ id: 'email' }), + Header: intl.get('email'), accessor: 'email', width: 150, }, { id: 'phone_number', - Header: formatMessage({ id: 'phone_number' }), + Header: intl.get('phone_number'), accessor: 'phone_number', width: 120, }, @@ -154,6 +155,6 @@ export const useUsersListColumns = () => { disableResizing: true, }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFloatingActions.js b/client/src/containers/Purchases/Bills/BillForm/BillFloatingActions.js index ad421fdac..38b20b3dd 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFloatingActions.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; diff --git a/client/src/containers/Purchases/Bills/BillForm/BillForm.js b/client/src/containers/Purchases/Bills/BillForm/BillForm.js index 12fc015ca..02f6103ca 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillForm.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillForm.js @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; import classNames from 'classnames'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { useHistory } from 'react-router-dom'; import { isEmpty, omit } from 'lodash'; import { CLASSES } from 'common/classes'; @@ -28,17 +28,11 @@ function BillForm({ // #withSettings baseCurrency, }) { - const { formatMessage } = useIntl(); const history = useHistory(); // Bill form context. - const { - bill, - isNewMode, - submitPayload, - createBillMutate, - editBillMutate, - } = useBillFormContext(); + const { bill, isNewMode, submitPayload, createBillMutate, editBillMutate } = + useBillFormContext(); // Initial values in create and edit mode. const initialValues = useMemo( @@ -61,7 +55,7 @@ function BillForm({ const handleErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === ERROR.BILL_NUMBER_EXISTS)) { setErrors({ - bill_number: formatMessage({ id: 'bill_number_exists' }), + bill_number: intl.get('bill_number_exists'), }); } }; @@ -78,9 +72,7 @@ function BillForm({ if (totalQuantity === 0) { AppToaster.show({ - message: formatMessage({ - id: 'quantity_cannot_be_zero_or_empty', - }), + message: intl.get('quantity_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); setSubmitting(false); @@ -94,12 +86,10 @@ function BillForm({ // Handle the request success. const onSuccess = (response) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_bill_has_been_created_successfully' - : 'the_bill_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_bill_has_been_created_successfully' + : 'the_bill_has_been_edited_successfully', { number: values.bill_number }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Purchases/Bills/BillForm/BillForm.schema.js b/client/src/containers/Purchases/Bills/BillForm/BillForm.schema.js index 8814c9e6e..2b0e32c08 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillForm.schema.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillForm.schema.js @@ -1,27 +1,27 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; import { isBlank } from 'utils'; const BillFormSchema = Yup.object().shape({ vendor_id: Yup.number() .required() - .label(formatMessage({ id: 'vendor_name_' })), + .label(intl.get('vendor_name_')), bill_date: Yup.date() .required() - .label(formatMessage({ id: 'bill_date_' })), + .label(intl.get('bill_date_')), due_date: Yup.date() .required() - .label(formatMessage({ id: 'due_date_' })), + .label(intl.get('due_date_')), bill_number: Yup.string() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'bill_number_' })), + .label(intl.get('bill_number_')), reference_no: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING), note: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), open: Yup.boolean(), entries: Yup.array().of( Yup.object().shape({ diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFormFooter.js b/client/src/containers/Purchases/Bills/BillForm/BillFormFooter.js index 4c34a5ba6..3f0259b0c 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFormFooter.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFormFooter.js @@ -1,6 +1,6 @@ import React from 'react'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField } from 'formik'; import classNames from 'classnames'; import { Postbox, Row, Col } from 'components'; @@ -12,7 +12,7 @@ import { inputIntent } from 'utils'; export default function BillFormFooter() { return (
- + } defaultOpen={false}> @@ -33,7 +33,7 @@ export default function BillFormFooter() { initialFiles={[]} // onDrop={onDropFiles} // onDeleteFile={onDropFiles} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} /> diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js b/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js index ea8ac2aeb..08676967b 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFormHeader.js @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; +import intl from 'react-intl-universal'; import { CLASSES } from 'common/classes'; @@ -28,7 +29,7 @@ function BillFormHeader({
diff --git a/client/src/containers/Purchases/Bills/BillForm/BillFormHeaderFields.js b/client/src/containers/Purchases/Bills/BillForm/BillFormHeaderFields.js index 6d2a286e2..8f5086849 100644 --- a/client/src/containers/Purchases/Bills/BillForm/BillFormHeaderFields.js +++ b/client/src/containers/Purchases/Bills/BillForm/BillFormHeaderFields.js @@ -1,7 +1,7 @@ import React from 'react'; import { FormGroup, InputGroup, Position } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField, ErrorMessage } from 'formik'; import classNames from 'classnames'; diff --git a/client/src/containers/Purchases/Bills/BillForm/utils.js b/client/src/containers/Purchases/Bills/BillForm/utils.js index 2658c3ec4..47ebec4a2 100644 --- a/client/src/containers/Purchases/Bills/BillForm/utils.js +++ b/client/src/containers/Purchases/Bills/BillForm/utils.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; import { transformToForm, repeatValue } from 'utils'; @@ -47,9 +47,7 @@ export const handleDeleteErrors = (errors) => { errors.find((error) => error.type === 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES') ) { AppToaster.show({ - message: formatMessage({ - id: 'cannot_delete_bill_that_has_payment_transactions', - }), + message: intl.get('cannot_delete_bill_that_has_payment_transactions'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js index 21821cfae..0a1b6c3bf 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.js @@ -13,7 +13,8 @@ import { import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import { If, DashboardActionViewsList } from 'components'; @@ -33,7 +34,7 @@ function BillActionsBar({ const history = useHistory(); // React intl. - const { formatMessage } = useIntl(); + // Bills list context. const { billsViews } = useBillsListContext(); @@ -79,7 +80,7 @@ function BillActionsBar({ true ? ( ) : ( - `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + `${filterCount} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Purchases/Bills/BillsLanding/BillsEmptyStatus.js b/client/src/containers/Purchases/Bills/BillsLanding/BillsEmptyStatus.js index fd42a941d..cecfef2ad 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/BillsEmptyStatus.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/BillsEmptyStatus.js @@ -2,16 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function BillsEmptyStatus() { const history = useHistory(); return ( } description={

- Here a list of your organization products and services, to be used when you create invoices or bills to your customers or vendors. +

} action={ @@ -23,11 +24,11 @@ export default function BillsEmptyStatus() { history.push('/bills/new'); }} > - New bill + } diff --git a/client/src/containers/Purchases/Bills/BillsLanding/components.js b/client/src/containers/Purchases/Bills/BillsLanding/components.js index f08bfc18c..34d299000 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/components.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/components.js @@ -10,7 +10,8 @@ import { Tag, ProgressBar, } from '@blueprintjs/core'; -import { useIntl, FormattedMessage as T } from 'react-intl'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T } from 'components'; import { Icon, If, Choose, Money } from 'components'; import { safeCallback, isBlank, calculateStatus } from 'utils'; import moment from 'moment'; @@ -22,38 +23,36 @@ export function ActionsMenu({ payload: { onEdit, onOpen, onDelete, onQuick }, row: { original }, }) { - const { formatMessage } = useIntl(); - return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_bill' })} + text={intl.get('edit_bill')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'mark_as_opened' })} + text={intl.get('mark_as_opened')} onClick={safeCallback(onOpen, original)} /> } - text={formatMessage({ id: 'add_payment' })} + text={intl.get('add_payment')} onClick={safeCallback(onQuick, original)} /> } @@ -92,28 +91,25 @@ export function StatusAccessor(bill) { - + {intl.get('overdue_by', { overdue: bill.overdue_days })} - + {intl.get('due_in', { due: bill.remaining_days })} - - ), - }} - /> + {intl.get('day_partially_paid', { + due: ( + + ), + })} [ { id: 'bill_date', - Header: formatMessage({ id: 'bill_date' }), + Header: intl.get('bill_date'), accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'), width: 110, className: 'bill_date', }, { id: 'vendor', - Header: formatMessage({ id: 'vendor_name' }), + Header: intl.get('vendor_name'), accessor: 'vendor.display_name', width: 180, className: 'vendor', }, { id: 'bill_number', - Header: formatMessage({ id: 'bill_number' }), + Header: intl.get('bill_number'), accessor: (row) => (row.bill_number ? `#${row.bill_number}` : null), width: 100, className: 'bill_number', }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: AmountAccessor, width: 120, className: 'amount', }, { id: 'status', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: StatusAccessor, width: 160, className: 'status', }, { id: 'due_date', - Header: formatMessage({ id: 'due_date' }), + Header: intl.get('due_date'), accessor: (r) => moment(r.due_date).format('YYYY MMM DD'), width: 110, className: 'due_date', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference_no', width: 90, className: 'reference_no', }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js index 2a7383042..feb2615ff 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.js @@ -1,6 +1,7 @@ import React, { useCallback } from 'react'; import { CloudLoadingIndicator } from 'components'; import classNames from 'classnames'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import { DataTableEditable } from 'components'; @@ -42,9 +43,15 @@ export default function PaymentMadeEntriesTable({ // Detarmines the right no results message before selecting vendor and aftering // selecting vendor id. - const noResultsMessage = vendor_id - ? 'There is no payable bills for this vendor that can be applied for this payment' - : 'Please select a vendor to display all open bills for it.'; + const noResultsMessage = vendor_id ? ( + + ) : ( + + ); return ( diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js index 768267760..b717fd76b 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import classNames from 'classnames'; import { useFormikContext } from 'formik'; diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js index ae1156448..98dc4522a 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField } from 'formik'; import { Postbox, Row, Col } from 'components'; import { CLASSES } from 'common/classes'; @@ -12,7 +12,7 @@ import { CLASSES } from 'common/classes'; export default function PaymentMadeFooter() { return (
- + } defaultOpen={false}> {/* --------- Statement --------- */} diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js index 682dba4fb..dae5993e1 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { sumBy, pick, defaultTo } from 'lodash'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -32,7 +32,6 @@ function PaymentMadeForm({ baseCurrency, }) { const history = useHistory(); - const { formatMessage } = useIntl(); // Payment made form context. const { @@ -80,10 +79,8 @@ function PaymentMadeForm({ if (totalPaymentAmount <= 0) { AppToaster.show({ - message: formatMessage({ - id: 'you_cannot_make_payment_with_zero_total_amount', - intent: Intent.WARNING, - }), + message: intl.get('you_cannot_make_payment_with_zero_total_amount'), + intent: Intent.WARNING, }); return; } @@ -92,11 +89,11 @@ function PaymentMadeForm({ // Triggers once the save request success. const onSaved = (response) => { AppToaster.show({ - message: formatMessage({ - id: isNewMode + message: intl.get( + isNewMode ? 'the_payment_made_has_been_edited_successfully' : 'the_payment_made_has_been_created_successfully', - }), + ), intent: Intent.SUCCESS, }); setSubmitting(false); @@ -117,7 +114,7 @@ function PaymentMadeForm({ if (getError(ERRORS.PAYMENT_NUMBER_NOT_UNIQUE)) { setFieldError( 'payment_number', - formatMessage({ id: 'payment_number_is_not_unique' }), + intl.get('payment_number_is_not_unique'), ); } setSubmitting(false); diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.schema.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.schema.js index 44fb289f5..0e585f8c4 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.schema.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.schema.js @@ -1,22 +1,22 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ vendor_id: Yup.string() - .label(formatMessage({ id: 'vendor_name_' })) + .label(intl.get('vendor_name_')) .required(), payment_date: Yup.date() .required() - .label(formatMessage({ id: 'payment_date_' })), + .label(intl.get('payment_date_')), payment_account_id: Yup.number() .required() - .label(formatMessage({ id: 'payment_account_' })), + .label(intl.get('payment_account_')), payment_number: Yup.string() .nullable() .max(DATATYPES_LENGTH.STRING) .nullable() - .label(formatMessage({ id: 'payment_no_' })), + .label(intl.get('payment_no_')), reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), description: Yup.string().max(DATATYPES_LENGTH.TEXT), entries: Yup.array().of( diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.js index 528524842..43ffbca87 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.js @@ -4,9 +4,9 @@ import { useFormikContext } from 'formik'; import { sumBy } from 'lodash'; import { CLASSES } from 'common/classes'; import { compose } from 'utils'; -import { - Money, -} from 'components'; +import { Money } from 'components'; +import { FormattedMessage as T } from 'components'; + import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields'; import withSettings from 'containers/Settings/withSettings'; @@ -18,7 +18,9 @@ function PaymentMadeFormHeader({ baseCurrency, }) { // Formik form context. - const { values: { entries } } = useFormikContext(); + const { + values: { entries }, + } = useFormikContext(); // Calculate the payment amount of the entries. const amountPaid = useMemo(() => sumBy(entries, 'payment_amount'), [entries]); @@ -30,7 +32,9 @@ function PaymentMadeFormHeader({
- Amount Received + + +

diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js index d107cdb5e..88fdb5652 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js @@ -9,7 +9,7 @@ import { } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { toSafeInteger } from 'lodash'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -157,7 +157,7 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) { small={true} minimal={true} > - Receive full amount ( + ( ) diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js index 59f949091..230c97a76 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import moment from 'moment'; import { Money } from 'components'; import { safeSumBy, formattedAmount } from 'utils'; @@ -51,7 +51,7 @@ function MoneyTableCell({ row: { original }, value }) { * Payment made entries table columns */ export function usePaymentMadeEntriesTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ @@ -65,7 +65,7 @@ export function usePaymentMadeEntriesTableColumns() { className: 'index', }, { - Header: formatMessage({ id: 'Date' }), + Header: intl.get('Date'), id: 'bill_date', accessor: 'bill_date', Cell: BillDateCell, @@ -74,13 +74,13 @@ export function usePaymentMadeEntriesTableColumns() { className: 'bill_date', }, { - Header: formatMessage({ id: 'bill_number' }), + Header: intl.get('bill_number'), accessor: BillNumberAccessor, disableSortBy: true, className: 'bill_number', }, { - Header: formatMessage({ id: 'bill_amount' }), + Header: intl.get('bill_amount'), accessor: 'amount', Cell: MoneyTableCell, Footer: AmountFooterCell, @@ -88,7 +88,7 @@ export function usePaymentMadeEntriesTableColumns() { className: 'amount', }, { - Header: formatMessage({ id: 'amount_due' }), + Header: intl.get('amount_due'), accessor: 'due_amount', Cell: MoneyTableCell, Footer: DueAmountFooterCell, @@ -96,7 +96,7 @@ export function usePaymentMadeEntriesTableColumns() { className: 'due_amount', }, { - Header: formatMessage({ id: 'payment_amount' }), + Header: intl.get('payment_amount'), accessor: 'payment_amount', Cell: MoneyFieldCell, Footer: PaymentAmountFooterCell, @@ -104,6 +104,6 @@ export function usePaymentMadeEntriesTableColumns() { className: 'payment_amount', }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js index ebf4cd447..1f4271733 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.js @@ -13,7 +13,8 @@ import { import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import { If, DashboardActionViewsList } from 'components'; @@ -31,7 +32,7 @@ function PaymentMadeActionsBar({ setPaymentMadesTableState, }) { const history = useHistory(); - const { formatMessage } = useIntl(); + // Payment receives list context. const { paymentMadesViews } = usePaymentMadesListContext(); @@ -73,7 +74,7 @@ function PaymentMadeActionsBar({ true ? ( ) : ( - `${0} ${formatMessage({ id: 'filters_applied' })}` + `${0} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeViewTabs.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeViewTabs.js index bc69f5c41..346d84072 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeViewTabs.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeViewTabs.js @@ -1,6 +1,6 @@ import React from 'react'; import { useHistory } from 'react-router'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core'; import { pick } from 'lodash'; diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesEmptyStatus.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesEmptyStatus.js index 8ed4582af..3f008d876 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesEmptyStatus.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadesEmptyStatus.js @@ -2,17 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function PaymentMadesEmptyStatus() { const history = useHistory(); return ( } description={

- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. +

} action={ @@ -24,11 +24,11 @@ export default function PaymentMadesEmptyStatus() { history.push('/payment-mades/new'); }} > - New bill payment + } diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js index f5e00ac41..081267a2e 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js @@ -9,7 +9,7 @@ import { MenuDivider, Position, } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Icon, Money } from 'components'; import { safeCallback } from 'utils'; @@ -28,22 +28,22 @@ export function ActionsMenu({ row: { original }, payload: { onEdit, onDelete }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_payment_made' })} + text={intl.get('edit_payment_made')} onClick={safeCallback(onEdit, original)} /> } @@ -70,13 +70,13 @@ export function ActionsCell(props) { * Retrieve payment mades table columns. */ export function usePaymentMadesTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'payment_date', - Header: formatMessage({ id: 'payment_date' }), + Header: intl.get('payment_date'), Cell: DateCell, accessor: 'payment_date', width: 140, @@ -84,14 +84,14 @@ export function usePaymentMadesTableColumns() { }, { id: 'vendor', - Header: formatMessage({ id: 'vendor_name' }), + Header: intl.get('vendor_name'), accessor: 'vendor.display_name', width: 140, className: 'vendor_id', }, { id: 'payment_number', - Header: formatMessage({ id: 'payment_number' }), + Header: intl.get('payment_number'), accessor: (row) => row.payment_number ? `#${row.payment_number}` : null, width: 140, @@ -99,26 +99,26 @@ export function usePaymentMadesTableColumns() { }, { id: 'payment_account', - Header: formatMessage({ id: 'payment_account' }), + Header: intl.get('payment_account'), accessor: 'payment_account.name', width: 140, className: 'payment_account_id', }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: AmountAccessor, width: 140, className: 'amount', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference' }), + Header: intl.get('reference'), accessor: 'reference', width: 140, className: 'reference', }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/QuickNewDropdown/QuickNewDropdown.js b/client/src/containers/QuickNewDropdown/QuickNewDropdown.js index 5a02eefaa..a3489d21d 100644 --- a/client/src/containers/QuickNewDropdown/QuickNewDropdown.js +++ b/client/src/containers/QuickNewDropdown/QuickNewDropdown.js @@ -1,22 +1,25 @@ import React from 'react'; import { Button, MenuItem } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import { Icon } from 'components'; import { Position } from '@blueprintjs/core'; -import quickNewOptions from 'common/quickNewOptions'; +import { getQuickNewActions } from 'common/quickNewOptions'; import { Select } from '@blueprintjs/select'; /** * Quick New Dropdown. */ -function QuickNewDropdown() { +export default function QuickNewDropdown() { const history = useHistory(); + const quickNewOptions = getQuickNewActions(); + // Handle click quick new button. const handleClickQuickNew = ({ path }) => { history.push(`/${path}`); }; + // Item renderer. const itemRenderer = (item, { handleClick, modifiers, query }) => ( ); @@ -37,6 +40,4 @@ function QuickNewDropdown() { /> ); -} - -export default QuickNewDropdown; +} \ No newline at end of file diff --git a/client/src/containers/Sales/Estimate/EstimatesDataTable.js b/client/src/containers/Sales/Estimate/EstimatesDataTable.js index 1e2e2b721..3d3e0ce24 100644 --- a/client/src/containers/Sales/Estimate/EstimatesDataTable.js +++ b/client/src/containers/Sales/Estimate/EstimatesDataTable.js @@ -10,7 +10,8 @@ import { Tag, } from '@blueprintjs/core'; import classNames from 'classnames'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import moment from 'moment'; import { CLASSES } from 'common/classes'; @@ -49,7 +50,7 @@ function EstimatesDataTable({ onDrawerEstimate, onSelectedRowsChange, }) { - const { formatMessage } = useIntl(); + const isLoaded = useIsValuePassed(estimatesLoading, false); const handleEditEstimate = useCallback( @@ -71,17 +72,17 @@ function EstimatesDataTable({ } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_estimate' })} + text={intl.get('edit_estimate')} onClick={handleEditEstimate(estimate)} /> onDeliverEstimate(estimate)} /> @@ -90,7 +91,7 @@ function EstimatesDataTable({ condition={estimate.is_delivered && estimate.is_approved} > onRejectEstimate(estimate)} /> @@ -98,35 +99,35 @@ function EstimatesDataTable({ condition={estimate.is_delivered && estimate.is_rejected} > onApproveEstimate(estimate)} /> onApproveEstimate(estimate)} /> onRejectEstimate(estimate)} /> onDrawerEstimate()} /> } /> ), - [handleDeleteEstimate, handleEditEstimate, formatMessage], + [handleDeleteEstimate, handleEditEstimate], ); const onRowContextMenu = useCallback( @@ -140,28 +141,28 @@ function EstimatesDataTable({ () => [ { id: 'estimate_date', - Header: formatMessage({ id: 'estimate_date' }), + Header: intl.get('estimate_date'), accessor: (r) => moment(r.estimate_date).format('YYYY MMM DD'), width: 140, className: 'estimate_date', }, { id: 'customer_id', - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'customer.display_name', width: 140, className: 'customer_id', }, { id: 'expiration_date', - Header: formatMessage({ id: 'expiration_date' }), + Header: intl.get('expiration_date'), accessor: (r) => moment(r.expiration_date).format('YYYY MMM DD'), width: 140, className: 'expiration_date', }, { id: 'estimate_number', - Header: formatMessage({ id: 'estimate_number' }), + Header: intl.get('estimate_number'), accessor: (row) => row.estimate_number ? `#${row.estimate_number}` : null, width: 140, @@ -169,7 +170,7 @@ function EstimatesDataTable({ }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: (r) => , width: 140, @@ -178,7 +179,7 @@ function EstimatesDataTable({ { id: 'status', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: (row) => statusAccessor(row), width: 140, className: 'status', @@ -186,7 +187,7 @@ function EstimatesDataTable({ { id: 'reference', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference', width: 140, className: 'reference', @@ -208,7 +209,7 @@ function EstimatesDataTable({ disableResizing: true, }, ], - [actionMenuList, formatMessage], + [actionMenuList], ); const handleFetchData = useCallback( diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.js index 0433b8e9a..a493a59fc 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js index 1a37ed095..1b8e540ce 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.js @@ -1,7 +1,8 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { omit, sumBy, isEmpty } from 'lodash'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -36,7 +37,6 @@ function EstimateForm({ estimateIncrementMode, baseCurrency, }) { - const { formatMessage } = useIntl(); const history = useHistory(); const { estimate, @@ -72,9 +72,7 @@ function EstimateForm({ const handleErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === ERROR.ESTIMATE_NUMBER_IS_NOT_UNQIUE)) { setErrors({ - estimate_number: formatMessage({ - id: 'estimate_number_is_not_unqiue', - }), + estimate_number: intl.get('estimate_number_is_not_unqiue'), }); } }; @@ -93,9 +91,7 @@ function EstimateForm({ if (totalQuantity === 0) { AppToaster.show({ - message: formatMessage({ - id: 'quantity_cannot_be_zero_or_empty', - }), + message: intl.get('quantity_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); setSubmitting(false); @@ -111,12 +107,10 @@ function EstimateForm({ }; const onSuccess = (response) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_estimate_has_been_edited_successfully' - : 'the_estimate_has_been_created_successfully', - }, + message: intl.get( + isNewMode + ? 'the_estimate_has_been_edited_successfully' + : 'the_estimate_has_been_created_successfully', { number: values.estimate_number }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.schema.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.schema.js index 79623527d..36e12468d 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.schema.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateForm.schema.js @@ -1,32 +1,32 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; import { isBlank } from 'utils'; const Schema = Yup.object().shape({ customer_id: Yup.number() - .label(formatMessage({ id: 'customer_name_' })) + .label(intl.get('customer_name_')) .required(), estimate_date: Yup.date() .required() - .label(formatMessage({ id: 'estimate_date_' })), + .label(intl.get('estimate_date_')), expiration_date: Yup.date() .required() - .label(formatMessage({ id: 'expiration_date_' })), + .label(intl.get('expiration_date_')), estimate_number: Yup.string() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'estimate_number_' })), + .label(intl.get('estimate_number_')), reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), note: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), terms_conditions: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), delivered: Yup.boolean(), entries: Yup.array().of( Yup.object().shape({ diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooter.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooter.js index af311ced4..f8c290f3b 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooter.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooter.js @@ -1,6 +1,6 @@ import React from 'react'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField } from 'formik'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -15,7 +15,7 @@ import { inputIntent } from 'utils'; export default function EstiamteFormFooter({}) { return (
- + } defaultOpen={false}> {/* --------- Customer Note --------- */} @@ -50,7 +50,7 @@ export default function EstiamteFormFooter({}) { initialFiles={[]} // onDrop={handleDropFiles} // onDeleteFile={handleDeleteFile} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} /> diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js index cf95b5a09..91da74c0a 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeader.js @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; +import intl from 'react-intl-universal'; import { CLASSES } from 'common/classes'; @@ -18,15 +19,16 @@ function EstimateFormHeader({ const { values } = useFormikContext(); // Calculate the total due amount of bill entries. - const totalDueAmount = useMemo(() => sumBy(values.entries, 'total'), [ - values.entries, - ]); + const totalDueAmount = useMemo( + () => sumBy(values.entries, 'total'), + [values.entries], + ); return (
diff --git a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.js b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.js index 3c33a93fd..92248ae78 100644 --- a/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.js +++ b/client/src/containers/Sales/Estimates/EstimateForm/EstimateFormHeaderFields.js @@ -6,7 +6,7 @@ import { ControlGroup, } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField, ErrorMessage } from 'formik'; import { momentFormatter, @@ -170,7 +170,7 @@ function EstimateFormHeader({ }} tooltip={true} tooltipProps={{ - content: 'Setting your auto-generated estimate number', + content: , position: Position.BOTTOM_LEFT, }} /> diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js index cd5fc4d52..19690e79e 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.js @@ -12,7 +12,8 @@ import { } from '@blueprintjs/core'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { If, DashboardActionViewsList } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -30,7 +31,7 @@ function EstimateActionsBar({ setEstimatesTableState, }) { const history = useHistory(); - const { formatMessage } = useIntl(); + const [filterCount, setFilterCount] = useState(0); @@ -75,7 +76,7 @@ function EstimateActionsBar({ filterCount <= 0 ? ( ) : ( - `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + `${filterCount} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesEmptyStatus.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesEmptyStatus.js index 7b49174bd..c2e5e92fa 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesEmptyStatus.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesEmptyStatus.js @@ -2,17 +2,16 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function EstimatesEmptyStatus() { const history = useHistory(); - return ( } description={

- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. +

} action={ @@ -24,10 +23,10 @@ export default function EstimatesEmptyStatus() { history.push('/estimates/new'); }} > - New sale estimate + } diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js index 2dba0f690..b6e92fdfe 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js @@ -10,7 +10,8 @@ import { Position, } from '@blueprintjs/core'; import { Money, Choose, Icon, If } from 'components'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { safeCallback } from 'utils'; import moment from 'moment'; @@ -59,29 +60,29 @@ export function ActionsMenu({ onConvert, }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_estimate' })} + text={intl.get('edit_estimate')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'convert_to_invoice' })} + text={intl.get('convert_to_invoice')} onClick={safeCallback(onConvert, original)} /> } - text={formatMessage({ id: 'mark_as_delivered' })} + text={intl.get('mark_as_delivered')} onClick={safeCallback(onDeliver, original)} /> @@ -89,37 +90,37 @@ export function ActionsMenu({ } - text={formatMessage({ id: 'mark_as_rejected' })} + text={intl.get('mark_as_rejected')} onClick={safeCallback(onReject, original)} /> } - text={formatMessage({ id: 'mark_as_approved' })} + text={intl.get('mark_as_approved')} onClick={safeCallback(onApprove, original)} /> } - text={formatMessage({ id: 'mark_as_approved' })} + text={intl.get('mark_as_approved')} onClick={safeCallback(onApprove, original)} /> } - text={formatMessage({ id: 'mark_as_rejected' })} + text={intl.get('mark_as_rejected')} onClick={safeCallback(onReject, original)} /> } - text={formatMessage({ id: 'estimate_paper' })} + text={intl.get('estimate_paper')} onClick={safeCallback(onDrawer, original)} /> } @@ -148,13 +149,13 @@ function ActionsCell(props) { } export function useEstiamtesTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'estimate_date', - Header: formatMessage({ id: 'estimate_date' }), + Header: intl.get('estimate_date'), accessor: 'estimate_date', Cell: DateCell, width: 140, @@ -162,14 +163,14 @@ export function useEstiamtesTableColumns() { }, { id: 'customer', - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'customer.display_name', width: 140, className: 'customer_id', }, { id: 'expiration_date', - Header: formatMessage({ id: 'expiration_date' }), + Header: intl.get('expiration_date'), accessor: 'expiration_date', Cell: DateCell, width: 140, @@ -177,7 +178,7 @@ export function useEstiamtesTableColumns() { }, { id: 'estimate_number', - Header: formatMessage({ id: 'estimate_number' }), + Header: intl.get('estimate_number'), accessor: (row) => row.estimate_number ? `#${row.estimate_number}` : null, width: 140, @@ -185,26 +186,26 @@ export function useEstiamtesTableColumns() { }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: AmountAccessor, width: 140, className: 'amount', }, { id: 'status', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: (row) => statusAccessor(row), width: 140, className: 'status', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference', width: 90, className: 'reference', } ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Sales/Invoices/InvoiceDetails/InvoicePaper.js b/client/src/containers/Sales/Invoices/InvoiceDetails/InvoicePaper.js index 2e0d01757..726a5f3da 100644 --- a/client/src/containers/Sales/Invoices/InvoiceDetails/InvoicePaper.js +++ b/client/src/containers/Sales/Invoices/InvoiceDetails/InvoicePaper.js @@ -1,19 +1,20 @@ import React from 'react'; import { useInvoiceDrawerContext } from './InvoiceDrawerProvider'; import PaperTemplate from 'containers/Drawers/PaperTemplate/PaperTemplate'; +import intl from 'react-intl-universal'; export default function InvoicePaper() { const { invoice, entries } = useInvoiceDrawerContext(); const propLabels = { labels: { - name: 'Invoice', - billedTo: 'Billed to', - date: 'Invoice date', - refNo: 'Invoice No.', - billedFrom: 'Billed from', - amount: 'Invoice amount', - dueDate: 'Due date', + name: intl.get('invoice'), + billedTo: intl.get('billed_to'), + date: intl.get('invoice_date_'), + refNo: intl.get('invoice_no__'), + billedFrom: intl.get('billed_from'), + amount: intl.get('invoice_amount'), + dueDate: intl.get('due_date_'), }, }; diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.js index 0db8607f6..ecaa99018 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.js @@ -10,7 +10,7 @@ import { MenuItem, } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; import classNames from 'classnames'; diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js index 5c5e642ce..20779cba9 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js @@ -1,14 +1,14 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { sumBy, omit, isEmpty } from 'lodash'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; import { - CreateInvoiceFormSchema, - EditInvoiceFormSchema, + getCreateInvoiceFormSchema, + getEditInvoiceFormSchema, } from './InvoiceForm.schema'; import InvoiceFormHeader from './InvoiceFormHeader'; @@ -36,7 +36,6 @@ function InvoiceForm({ invoiceIncrementMode, baseCurrency, }) { - const { formatMessage } = useIntl(); const history = useHistory(); // Invoice form context. @@ -85,7 +84,7 @@ function InvoiceForm({ // Throw danger toaster in case total quantity equals zero. if (totalQuantity === 0) { AppToaster.show({ - message: formatMessage({ id: 'quantity_cannot_be_zero_or_empty' }), + message: intl.get('quantity_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); setSubmitting(false); @@ -103,12 +102,10 @@ function InvoiceForm({ // Handle the request success. const onSuccess = () => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_invoice_has_been_created_successfully' - : 'the_invoice_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_invoice_has_been_created_successfully' + : 'the_invoice_has_been_edited_successfully', { number: values.invoice_no }, ), intent: Intent.SUCCESS, @@ -141,6 +138,9 @@ function InvoiceForm({ } }; + const CreateInvoiceFormSchema = getCreateInvoiceFormSchema(); + const EditInvoiceFormSchema = getEditInvoiceFormSchema(); + return (
Yup.object().shape({ customer_id: Yup.string() - .label(formatMessage({ id: 'customer_name_' })) + .label(intl.get('customer_name_')) .required(), invoice_date: Yup.date() .required() - .label(formatMessage({ id: 'invoice_date_' })), + .label(intl.get('invoice_date_')), due_date: Yup.date() .required() - .label(formatMessage({ id: 'due_date_' })), + .label(intl.get('due_date_')), invoice_no: Yup.string() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'invoice_no_' })), + .label(intl.get('invoice_no_')), reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING), delivered: Yup.boolean(), from_estimate_id: Yup.string(), @@ -23,12 +23,12 @@ const Schema = Yup.object().shape({ .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), terms_conditions: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), entries: Yup.array().of( Yup.object().shape({ quantity: Yup.number() @@ -51,5 +51,5 @@ const Schema = Yup.object().shape({ ), }); -export const CreateInvoiceFormSchema = Schema; -export const EditInvoiceFormSchema = Schema; +export const getCreateInvoiceFormSchema = getSchema; +export const getEditInvoiceFormSchema = getSchema; diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooter.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooter.js index 81a1087ad..b25259b6c 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooter.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooter.js @@ -2,7 +2,7 @@ import React from 'react'; import { FastField } from 'formik'; import classNames from 'classnames'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import { Row, Col, Postbox } from 'components'; import Dragzone from 'components/Dragzone'; @@ -12,7 +12,7 @@ import { inputIntent } from 'utils'; export default function InvoiceFormFooter() { return (
- + } defaultOpen={false}> {/* --------- Invoice message --------- */} @@ -47,7 +47,7 @@ export default function InvoiceFormFooter() { initialFiles={[]} // onDrop={handleDropFiles} // onDeleteFile={handleDeleteFile} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} /> diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js index 37b18e5eb..18b958b18 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.js @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; +import intl from 'react-intl-universal'; import { CLASSES } from 'common/classes'; import InvoiceFormHeaderFields from './InvoiceFormHeaderFields'; @@ -21,15 +22,16 @@ function InvoiceFormHeader({ const { values } = useFormikContext(); // Calculate the total due amount of invoice entries. - const totalDueAmount = useMemo(() => sumBy(values.entries, 'total'), [ - values.entries, - ]); + const totalDueAmount = useMemo( + () => sumBy(values.entries, 'total'), + [values.entries], + ); return (
diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.js index db28cada2..c28ccb1e6 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeaderFields.js @@ -7,7 +7,7 @@ import { } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; import { FastField, Field, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { momentFormatter, compose, tansformDateValue } from 'utils'; import classNames from 'classnames'; import { useObserveInvoiceNoSettings } from './utils'; @@ -168,7 +168,7 @@ function InvoiceFormHeaderFields({ }} tooltip={true} tooltipProps={{ - content: 'Setting your auto-generated invoice number', + content: , position: Position.BOTTOM_LEFT, }} /> diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/utils.js b/client/src/containers/Sales/Invoices/InvoiceForm/utils.js index 536ff4792..6dde8e128 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/utils.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/utils.js @@ -12,7 +12,7 @@ import { useFormikContext } from 'formik'; import { Intent } from '@blueprintjs/core'; import { orderingLinesIndexes } from 'utils'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { ERROR } from 'common/errors'; import { AppToaster } from 'components'; @@ -66,7 +66,7 @@ export function transformToEditForm(invoice) { export const transformErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === ERROR.SALE_INVOICE_NUMBER_IS_EXISTS)) { setErrors({ - invoice_no: formatMessage({ id: 'sale_invoice_number_is_exists' }), + invoice_no: intl.get('sale_invoice_number_is_exists'), }); } if ( @@ -76,9 +76,7 @@ export const transformErrors = (errors, { setErrors }) => { ) ) { AppToaster.show({ - message: formatMessage({ - id: 'sale_estimate_is_already_converted_to_invoice', - }), + message: intl.get('sale_estimate_is_already_converted_to_invoice'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js index be25fab09..6ab383a12 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.js @@ -13,7 +13,8 @@ import { import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -32,7 +33,7 @@ function InvoiceActionsBar({ setInvoicesTableState, }) { const history = useHistory(); - const { formatMessage } = useIntl(); + const [filterCount, setFilterCount] = useState(0); @@ -76,7 +77,7 @@ function InvoiceActionsBar({ filterCount <= 0 ? ( ) : ( - `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + `${filterCount} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesEmptyStatus.js b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesEmptyStatus.js index 80bd64d3b..21212a4c2 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesEmptyStatus.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/InvoicesEmptyStatus.js @@ -1,37 +1,37 @@ - import React from 'react'; - import { Button, Intent } from '@blueprintjs/core'; - import { useHistory } from 'react-router-dom'; - import { EmptyStatus } from 'components'; +import React from 'react'; +import { Button, Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; +import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; - export default function EstimatesEmptyStatus() { - const history = useHistory(); +export default function EstimatesEmptyStatus() { + const history = useHistory(); - return ( - - It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. -

- } - action={ - <> - + return ( + } + description={ +

+ +

+ } + action={ + <> + - - - } - /> - ); - } + + + } + /> + ); +} diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js index 91b40c45f..4cc6acc46 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js @@ -10,11 +10,12 @@ import { Position, Button, } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; + +import { FormattedMessage as T } from 'components'; import moment from 'moment'; import { Choose, If, Icon } from 'components'; import { Money, AppToaster } from 'components'; -import { formatMessage } from 'services/intl'; import { safeCallback, calculateStatus } from 'utils'; export const statusAccessor = (row) => { @@ -34,29 +35,23 @@ export const statusAccessor = (row) => { - + {intl.get('overdue_by', { overdue: row.overdue_days })} - + {intl.get('due_in', { due: row.remaining_days })} - - ), - }} - /> + {intl.get('day_partially_paid', { + due: ( + + ), + })} { ) ) { AppToaster.show({ - message: formatMessage({ - id: 'the_invoice_cannot_be_deleted', - }), + message: intl.get('the_invoice_cannot_be_deleted'), intent: Intent.DANGER, }); } @@ -95,9 +88,7 @@ export const handleDeleteErrors = (errors) => { ) ) { AppToaster.show({ - message: formatMessage({ - id: 'the_payment_amount_that_received', - }), + message: intl.get('the_payment_amount_that_received'), intent: Intent.DANGER, }); } @@ -107,41 +98,39 @@ export function ActionsMenu({ payload: { onEdit, onDeliver, onDelete, onDrawer, onQuick }, row: { original }, }) { - const { formatMessage } = useIntl(); - return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_invoice' })} + text={intl.get('edit_invoice')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'mark_as_delivered' })} + text={intl.get('mark_as_delivered')} onClick={safeCallback(onDeliver, original)} /> } - text={formatMessage({ id: 'add_payment' })} + text={intl.get('add_payment')} onClick={safeCallback(onQuick, original)} /> } - text={formatMessage({ id: 'invoice_paper' })} + text={intl.get('invoice_paper')} onClick={safeCallback(onDrawer, original)} /> } @@ -165,20 +154,18 @@ function ActionsCell(props) { * Retrieve invoices table columns. */ export function useInvoicesTableColumns() { - const { formatMessage } = useIntl(); - return React.useMemo( () => [ { id: 'invoice_date', - Header: formatMessage({ id: 'invoice_date' }), + Header: intl.get('invoice_date'), accessor: (r) => moment(r.invoice_date).format('YYYY MMM DD'), width: 110, className: 'invoice_date', }, { id: 'customer', - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'customer.display_name', width: 180, className: 'customer_id', @@ -186,14 +173,14 @@ export function useInvoicesTableColumns() { { id: 'invoice_no', - Header: formatMessage({ id: 'invoice_no__' }), + Header: intl.get('invoice_no__'), accessor: 'invoice_no', width: 100, className: 'invoice_no', }, { id: 'balance', - Header: formatMessage({ id: 'balance' }), + Header: intl.get('balance'), accessor: (r) => ( ), @@ -202,26 +189,26 @@ export function useInvoicesTableColumns() { }, { id: 'status', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: (row) => statusAccessor(row), width: 160, className: 'status', }, { id: 'due_date', - Header: formatMessage({ id: 'due_date' }), + Header: intl.get('due_date'), accessor: (r) => moment(r.due_date).format('YYYY MMM DD'), width: 110, className: 'due_date', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference_no', width: 90, className: 'reference_no', }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js index bc71e8d9e..466c648c9 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useHistory } from 'react-router-dom'; import classNames from 'classnames'; import { useFormikContext } from 'formik'; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js index be606d09f..39eb827a7 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { omit, sumBy, pick, isEmpty, defaultTo } from 'lodash'; import { Intent } from '@blueprintjs/core'; import classNames from 'classnames'; @@ -40,7 +40,6 @@ function PaymentReceiveForm({ baseCurrency, }) { const history = useHistory(); - const { formatMessage } = useIntl(); // Payment receive form context. const { @@ -77,7 +76,7 @@ function PaymentReceiveForm({ nextPaymentNumber, paymentEntriesEditPage, paymentReceiveAutoIncrement, - preferredDepositAccount + preferredDepositAccount, ], ); @@ -100,9 +99,7 @@ function PaymentReceiveForm({ if (totalPaymentAmount <= 0) { AppToaster.show({ - message: formatMessage({ - id: 'you_cannot_make_payment_with_zero_total_amount', - }), + message: intl.get('you_cannot_make_payment_with_zero_total_amount'), intent: Intent.DANGER, }); setSubmitting(false); @@ -119,11 +116,11 @@ function PaymentReceiveForm({ // Handle request response success. const onSaved = (response) => { AppToaster.show({ - message: formatMessage({ - id: paymentReceiveId + message: intl.get( + paymentReceiveId ? 'the_payment_receive_transaction_has_been_edited' : 'the_payment_receive_transaction_has_been_created', - }), + ), intent: Intent.SUCCESS, }); setSubmitting(false); @@ -146,13 +143,13 @@ function PaymentReceiveForm({ if (getError('PAYMENT_RECEIVE_NO_EXISTS')) { setFieldError( 'payment_receive_no', - formatMessage({ id: 'payment_number_is_not_unique' }), + intl.get('payment_number_is_not_unique'), ); } if (getError('PAYMENT_RECEIVE_NO_REQUIRED')) { setFieldError( 'payment_receive_no', - formatMessage({ id: 'payment_receive_number_required' }), + intl.get('payment_receive_number_required'), ); } setSubmitting(false); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.schema.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.schema.js index 5a6072141..2067632e2 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.schema.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.schema.js @@ -1,22 +1,22 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; const Schema = Yup.object().shape({ customer_id: Yup.string() - .label(formatMessage({ id: 'customer_name_' })) + .label(intl.get('customer_name_')) .required(), payment_date: Yup.date() .required() - .label(formatMessage({ id: 'payment_date_' })), + .label(intl.get('payment_date_')), deposit_account_id: Yup.number() .required() - .label(formatMessage({ id: 'deposit_account_' })), + .label(intl.get('deposit_account_')), full_amount: Yup.number().nullable(), payment_receive_no: Yup.string() .nullable() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'payment_receive_no_' })), + .label(intl.get('payment_receive_no_')), reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), // statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), entries: Yup.array().of( diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFooter.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFooter.js index b9714c173..9b7bb9dfb 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFooter.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFooter.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import { FormGroup, TextArea } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField } from 'formik'; import { Row, Col, Postbox } from 'components'; import { CLASSES } from 'common/classes'; @@ -12,7 +12,7 @@ import { CLASSES } from 'common/classes'; export default function PaymentReceiveFormFooter({ getFieldProps }) { return (
- + } defaultOpen={false}> {/* --------- Statement --------- */} diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.js index ee2a95d4f..13da10ab9 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.js @@ -3,6 +3,7 @@ import { sumBy } from 'lodash'; import { useFormikContext } from 'formik'; import classNames from 'classnames'; import { Money } from 'components'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import PaymentReceiveHeaderFields from './PaymentReceiveHeaderFields'; @@ -33,7 +34,9 @@ function PaymentReceiveFormHeader({
- Amount Received + + +

diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js index 091d38e24..5fa0f1e11 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js @@ -7,7 +7,7 @@ import { Button, } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; import { useAutofocus } from 'hooks'; @@ -70,9 +70,10 @@ function PaymentReceiveHeaderFields({ const customerFieldRef = useAutofocus(); // Calculates the full-amount received. - const totalDueAmount = useMemo(() => safeSumBy(entries, 'due_amount'), [ - entries, - ]); + const totalDueAmount = useMemo( + () => safeSumBy(entries, 'due_amount'), + [entries], + ); // Handle receive full-amount link click. const handleReceiveFullAmountClick = () => { const newEntries = fullAmountPaymentEntries(entries); @@ -200,7 +201,7 @@ function PaymentReceiveHeaderFields({ small={true} minimal={true} > - Receive full amount ( + ( ) @@ -232,7 +233,11 @@ function PaymentReceiveHeaderFields({ }} tooltip={true} tooltipProps={{ - content: 'Setting your auto-generated Payment Receive number', + content: ( + + ), position: Position.BOTTOM_LEFT, }} /> diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js index 192ba8ecd..60b792364 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { CloudLoadingIndicator } from 'components'; import classNames from 'classnames'; import { useFormikContext } from 'formik'; +import { FormattedMessage as T } from 'components'; import { CLASSES } from 'common/classes'; import { usePaymentReceiveInnerContext } from './PaymentReceiveInnerProvider'; @@ -15,32 +16,37 @@ import { compose, updateTableRow } from 'utils'; export default function PaymentReceiveItemsTable({ entries, onUpdateData, - currencyCode + currencyCode, }) { // Payment receive form context. - const { - isDueInvoicesFetching, - } = usePaymentReceiveInnerContext(); + const { isDueInvoicesFetching } = usePaymentReceiveInnerContext(); // Payment receive entries form context. const columns = usePaymentReceiveEntriesColumns(); // Formik context. - const { values: { customer_id } } = useFormikContext(); + const { + values: { customer_id }, + } = useFormikContext(); // No results message. - const noResultsMessage = customer_id - ? 'There is no receivable invoices for this customer that can be applied for this payment' - : 'Please select a customer to display all open invoices for it.'; + const noResultsMessage = customer_id ? ( + + ) : ( + + ); // Handle update data. - const handleUpdateData = useCallback((rowIndex, columnId, value) => { - const newRows = compose( - updateTableRow(rowIndex, columnId, value), - )(entries); + const handleUpdateData = useCallback( + (rowIndex, columnId, value) => { + const newRows = compose(updateTableRow(rowIndex, columnId, value))( + entries, + ); - onUpdateData(newRows); - }, [entries, onUpdateData]); + onUpdateData(newRows); + }, + [entries, onUpdateData], + ); return ( @@ -53,11 +59,11 @@ export default function PaymentReceiveItemsTable({ payload={{ errors: [], updateData: handleUpdateData, - currencyCode + currencyCode, }} noResults={noResultsMessage} footer={true} /> ); -} \ No newline at end of file +} diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js index 87d63db8e..8941d6467 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js @@ -1,6 +1,7 @@ import React from 'react'; import moment from 'moment'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; + import { Money } from 'components'; import { MoneyFieldCell } from 'components/DataTableCells'; import { safeSumBy, formattedAmount } from 'utils'; @@ -58,14 +59,14 @@ function MoneyTableCell({ row: { original }, value }) { } function DateFooterCell() { - return 'Total'; + return intl.get('total') } /** * Retrieve payment receive form entries columns. */ export const usePaymentReceiveEntriesColumns = () => { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ @@ -79,7 +80,7 @@ export const usePaymentReceiveEntriesColumns = () => { className: 'index', }, { - Header: formatMessage({ id: 'Date' }), + Header: intl.get('Date'), id: 'invoice_date', accessor: 'invoice_date', Cell: InvoiceDateCell, @@ -90,13 +91,13 @@ export const usePaymentReceiveEntriesColumns = () => { className: 'date', }, { - Header: formatMessage({ id: 'invocie_number' }), + Header: intl.get('invocie_number'), accessor: InvNumberCellAccessor, disableSortBy: true, className: 'invoice_number', }, { - Header: formatMessage({ id: 'invoice_amount' }), + Header: intl.get('invoice_amount'), accessor: 'amount', Footer: BalanceFooterCell, Cell: MoneyTableCell, @@ -105,7 +106,7 @@ export const usePaymentReceiveEntriesColumns = () => { className: 'invoice_amount', }, { - Header: formatMessage({ id: 'amount_due' }), + Header: intl.get('amount_due'), accessor: 'due_amount', Footer: DueAmountFooterCell, Cell: MoneyTableCell, @@ -114,7 +115,7 @@ export const usePaymentReceiveEntriesColumns = () => { className: 'amount_due', }, { - Header: formatMessage({ id: 'payment_amount' }), + Header: intl.get('payment_amount'), accessor: 'payment_amount', Cell: MoneyFieldCell, Footer: PaymentAmountFooterCell, @@ -123,6 +124,6 @@ export const usePaymentReceiveEntriesColumns = () => { className: 'payment_amount', }, ], - [formatMessage], + [], ); }; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js index dd7a6d4f0..09cae6f7f 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.js @@ -13,7 +13,7 @@ import { import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveViewTabs.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveViewTabs.js index bbe9cb496..aade12cfc 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveViewTabs.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveViewTabs.js @@ -1,7 +1,7 @@ import React from 'react'; import { useHistory } from 'react-router'; import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { pick } from 'lodash'; import { DashboardViewsTabs } from 'components'; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesEmptyStatus.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesEmptyStatus.js index f307dc64a..6b0df1b31 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesEmptyStatus.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesEmptyStatus.js @@ -2,17 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function PaymentReceivesEmptyStatus() { const history = useHistory(); return ( } description={

- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. +

} action={ @@ -24,11 +24,11 @@ export default function PaymentReceivesEmptyStatus() { history.push('/payment-receives/new'); }} > - New payment receive + } diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js index 0735ba797..d31705c77 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js @@ -8,7 +8,8 @@ import { MenuDivider, Position, } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import moment from 'moment'; import { Money, Icon } from 'components'; import { safeCallback } from 'utils'; @@ -20,27 +21,27 @@ export function ActionsMenu({ row: { original: paymentReceive }, payload: { onEdit, onDelete, onDrawer }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_payment_receive' })} + text={intl.get('edit_payment_receive')} onClick={safeCallback(onEdit, paymentReceive)} /> } - text={formatMessage({ id: 'payment_receive_paper' })} + text={intl.get('payment_receive_paper')} onClick={safeCallback(onDrawer, paymentReceive)} /> } @@ -81,34 +82,34 @@ export function ActionsCell(props) { * Retrieve payment receives columns. */ export function usePaymentReceivesColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'payment_date', - Header: formatMessage({ id: 'payment_date' }), + Header: intl.get('payment_date'), accessor: PaymentDateAccessor, width: 140, className: 'payment_date', }, { id: 'customer', - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'customer.display_name', width: 160, className: 'customer_id', }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: AmountAccessor, width: 120, className: 'amount', }, { id: 'payment_receive_no', - Header: formatMessage({ id: 'payment_receive_no' }), + Header: intl.get('payment_receive_no'), accessor: (row) => row.payment_receive_no ? `#${row.payment_receive_no}` : null, width: 140, @@ -116,19 +117,19 @@ export function usePaymentReceivesColumns() { }, { id: 'deposit_account', - Header: formatMessage({ id: 'deposit_account' }), + Header: intl.get('deposit_account'), accessor: 'deposit_account.name', width: 140, className: 'deposit_account_id', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference_no', width: 140, className: 'reference_no', } ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Sales/Receipts/ReceiptDetails/ReceiptPaper.js b/client/src/containers/Sales/Receipts/ReceiptDetails/ReceiptPaper.js index 80473cd1f..7ad5c6f50 100644 --- a/client/src/containers/Sales/Receipts/ReceiptDetails/ReceiptPaper.js +++ b/client/src/containers/Sales/Receipts/ReceiptDetails/ReceiptPaper.js @@ -1,19 +1,20 @@ import React from 'react'; import { useReceiptDrawerContext } from './ReceiptDrawerProvider'; import PaperTemplate from 'containers/Drawers/PaperTemplate/PaperTemplate'; +import intl from 'react-intl-universal'; export default function ReceiptPaper() { const { receipt, entries } = useReceiptDrawerContext(); const propLabels = { labels: { - name: 'Receipt', - billedTo: 'Billed to', - date: 'Receipt date', - refNo: 'Receipt No.', - billedFrom: 'Billed from', - amount: 'Receipt amount', - dueDate: 'Due date', + name: intl.get('receipt_'), + billedTo: intl.get('billed_to'), + date: intl.get('receipt_date_'), + refNo: intl.get('receipt_no'), + billedFrom: intl.get('billed_from'), + amount: intl.get('receipt_amount'), + dueDate: intl.get('due_date_'), }, }; diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.js index 98de4dd1c..23a26f522 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { omit, sumBy, isEmpty } from 'lodash'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; @@ -39,7 +39,6 @@ function ReceiptForm({ preferredDepositAccount, baseCurrency, }) { - const { formatMessage } = useIntl(); const history = useHistory(); // Receipt form context. @@ -78,9 +77,7 @@ function ReceiptForm({ const handleErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === ERROR.SALE_RECEIPT_NUMBER_NOT_UNIQUE)) { setErrors({ - receipt_number: formatMessage({ - id: 'sale_receipt_number_not_unique', - }), + receipt_number: intl.get('sale_receipt_number_not_unique'), }); } }; @@ -97,9 +94,7 @@ function ReceiptForm({ if (totalQuantity === 0) { AppToaster.show({ - message: formatMessage({ - id: 'quantity_cannot_be_zero_or_empty', - }), + message: intl.get('quantity_cannot_be_zero_or_empty'), intent: Intent.DANGER, }); setSubmitting(false); @@ -117,12 +112,10 @@ function ReceiptForm({ // Handle the request success. const onSuccess = (response) => { AppToaster.show({ - message: formatMessage( - { - id: isNewMode - ? 'the_receipt_has_been_created_successfully' - : 'the_receipt_has_been_edited_successfully', - }, + message: intl.get( + isNewMode + ? 'the_receipt_has_been_created_successfully' + : 'the_receipt_has_been_edited_successfully', { number: values.receipt_number }, ), intent: Intent.SUCCESS, diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.schema.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.schema.js index 634ceff9c..647bf273a 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.schema.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.schema.js @@ -1,33 +1,33 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { DATATYPES_LENGTH } from 'common/dataTypes'; import { isBlank } from 'utils'; const Schema = Yup.object().shape({ customer_id: Yup.string() - .label(formatMessage({ id: 'customer_name_' })) + .label(intl.get('customer_name_')) .required(), receipt_date: Yup.date() .required() - .label(formatMessage({ id: 'receipt_date_' })), + .label(intl.get('receipt_date_')), receipt_number: Yup.string() .nullable() .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'receipt_no_' })), + .label(intl.get('receipt_no_')), deposit_account_id: Yup.number() .required() - .label(formatMessage({ id: 'deposit_account_' })), + .label(intl.get('deposit_account_')), reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING), receipt_message: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.STRING) - .label(formatMessage({ id: 'receipt_message_' })), + .label(intl.get('receipt_message_')), statement: Yup.string() .trim() .min(1) .max(DATATYPES_LENGTH.TEXT) - .label(formatMessage({ id: 'note' })), + .label(intl.get('note')), closed: Yup.boolean(), entries: Yup.array().of( Yup.object().shape({ diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.js index c39a8b78f..267bb13a1 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { useFormikContext } from 'formik'; import { useHistory } from 'react-router-dom'; import classNames from 'classnames'; diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooter.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooter.js index c3f651be7..5ad04e1f9 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooter.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooter.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormGroup, TextArea } from '@blueprintjs/core'; import { FastField } from 'formik'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Dragzone, Postbox, Row, Col } from 'components'; import { CLASSES } from 'common/classes'; import { inputIntent } from 'utils'; @@ -10,7 +10,7 @@ import { inputIntent } from 'utils'; export default function ReceiptFormFooter({}) { return (
- + } defaultOpen={false}> {/* --------- Receipt message --------- */} @@ -45,7 +45,8 @@ export default function ReceiptFormFooter({}) { initialFiles={[]} // onDrop={handleDropFiles} // onDeleteFile={handleDeleteFile} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} + /> diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js index 38e7ea290..87ee106e6 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeader.js @@ -7,6 +7,8 @@ import { CLASSES } from 'common/classes'; import ReceiptFormHeaderFields from './ReceiptFormHeaderFields'; import { PageFormBigNumber } from 'components'; +import intl from 'react-intl-universal'; + import withSettings from 'containers/Settings/withSettings'; import { compose } from 'redux'; @@ -22,9 +24,10 @@ function ReceiptFormHeader({ const { values } = useFormikContext(); // Calculate the total due amount of bill entries. - const totalDueAmount = useMemo(() => sumBy(values.entries, 'total'), [ - values.entries, - ]); + const totalDueAmount = useMemo( + () => sumBy(values.entries, 'total'), + [values.entries], + ); return (
@@ -32,7 +35,7 @@ function ReceiptFormHeader({ onReceiptNumberChanged={onReceiptNumberChanged} /> diff --git a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.js b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.js index a3e2ecb7b..7db0129d6 100644 --- a/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.js +++ b/client/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormHeaderFields.js @@ -6,7 +6,7 @@ import { ControlGroup, } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { FastField, ErrorMessage } from 'formik'; import { CLASSES } from 'common/classes'; @@ -65,10 +65,7 @@ function ReceiptFormHeader({ }; // Synsc receipt number settings with the form. - useObserveReceiptNoSettings( - receiptNumberPrefix, - receiptNextNumber, - ); + useObserveReceiptNoSettings(receiptNumberPrefix, receiptNextNumber); return (
@@ -175,7 +172,11 @@ function ReceiptFormHeader({ }} tooltip={true} tooltipProps={{ - content: 'Setting your auto-generated receipt number', + content: ( + + ), position: Position.BOTTOM_LEFT, }} inputProps={{ diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js index 0ac48fc00..3ee7e06af 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.js @@ -13,7 +13,8 @@ import { import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { If, DashboardActionViewsList } from 'components'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -31,7 +32,7 @@ function ReceiptActionsBar({ setReceiptsTableState, }) { const history = useHistory(); - const { formatMessage } = useIntl(); + const [filterCount, setFilterCount] = useState(0); @@ -77,7 +78,7 @@ function ReceiptActionsBar({ filterCount <= 0 ? ( ) : ( - `${filterCount} ${formatMessage({ id: 'filters_applied' })}` + `${filterCount} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsEmptyStatus.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsEmptyStatus.js index 2878dff45..e029ddf9b 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsEmptyStatus.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsEmptyStatus.js @@ -2,16 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function ReceiptsEmptyStatus() { const history = useHistory(); return ( } description={

- Here a list of your organization products and services, to be used when you create invoices or bills to your customers or vendors. +

} action={ @@ -23,11 +24,11 @@ export default function ReceiptsEmptyStatus() { history.push('/receipts/new'); }} > - New receipt + } diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js index f90073c1d..5b9542540 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Position, Menu, @@ -18,34 +19,34 @@ export function ActionsMenu({ payload: { onEdit, onDelete, onClose, onDrawer }, row: { original: receipt }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_receipt' })} + text={intl.get('edit_receipt')} onClick={safeCallback(onEdit, receipt)} /> } - text={formatMessage({ id: 'mark_as_closed' })} + text={intl.get('mark_as_closed')} onClick={safeCallback(onClose, receipt)} /> } - text={formatMessage({ id: 'receipt_paper' })} + text={intl.get('receipt_paper')} onClick={safeCallback(onDrawer, receipt)} /> } @@ -93,60 +94,60 @@ export function StatusAccessor(receipt) { * Retrieve receipts table columns. */ export function useReceiptsTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ { id: 'receipt_date', - Header: formatMessage({ id: 'receipt_date' }), + Header: intl.get('receipt_date'), accessor: (r) => moment(r.receipt_date).format('YYYY MMM DD'), width: 140, className: 'receipt_date', }, { id: 'customer', - Header: formatMessage({ id: 'customer_name' }), + Header: intl.get('customer_name'), accessor: 'customer.display_name', width: 140, className: 'customer_id', }, { id: 'receipt_number', - Header: formatMessage({ id: 'receipt_number' }), + Header: intl.get('receipt_number'), accessor: 'receipt_number', width: 140, className: 'receipt_number', }, { id: 'deposit_account', - Header: formatMessage({ id: 'deposit_account' }), + Header: intl.get('deposit_account'), accessor: 'deposit_account.name', width: 140, className: 'deposit_account', }, { id: 'amount', - Header: formatMessage({ id: 'amount' }), + Header: intl.get('amount'), accessor: (r) => , width: 140, className: 'amount', }, { id: 'status', - Header: formatMessage({ id: 'status' }), + Header: intl.get('status'), accessor: StatusAccessor, width: 140, className: 'status', }, { id: 'reference_no', - Header: formatMessage({ id: 'reference_no' }), + Header: intl.get('reference_no'), accessor: 'reference_no', width: 140, className: 'reference_no', }, ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Setup/SetupCongratsPage.js b/client/src/containers/Setup/SetupCongratsPage.js index e68334f27..de78fe0ce 100644 --- a/client/src/containers/Setup/SetupCongratsPage.js +++ b/client/src/containers/Setup/SetupCongratsPage.js @@ -1,7 +1,8 @@ import React, { useCallback } from 'react'; import { Button, Intent } from '@blueprintjs/core'; -import { useHistory } from "react-router-dom"; +import { useHistory } from 'react-router-dom'; import WorkflowIcon from './WorkflowIcon'; +import { FormattedMessage as T } from 'components'; import withOrganizationActions from 'containers/Organization/withOrganizationActions'; import 'style/pages/Setup/Congrats.scss'; @@ -11,18 +12,13 @@ import { compose } from 'utils'; /** * Setup congrats page. */ -function SetupCongratsPage({ - setOrganizationSetupCompleted, -}) { +function SetupCongratsPage({ setOrganizationSetupCompleted }) { const history = useHistory(); const handleBtnClick = useCallback(() => { setOrganizationSetupCompleted(false); history.push('/homepage'); - }, [ - setOrganizationSetupCompleted, - history, - ]); + }, [setOrganizationSetupCompleted, history]); return (
@@ -31,24 +27,20 @@ function SetupCongratsPage({
-

Congrats! You are ready to go

+

+ +

- It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. +

-
); } -export default compose( - withOrganizationActions, -)(SetupCongratsPage); \ No newline at end of file +export default compose(withOrganizationActions)(SetupCongratsPage); diff --git a/client/src/containers/Setup/SetupInitializingForm.js b/client/src/containers/Setup/SetupInitializingForm.js index 09fbe601b..9b926bee7 100644 --- a/client/src/containers/Setup/SetupInitializingForm.js +++ b/client/src/containers/Setup/SetupInitializingForm.js @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { ProgressBar, Intent } from '@blueprintjs/core'; import { useBuildTenant } from 'hooks/query'; +import { FormattedMessage as T } from 'components'; import 'style/pages/Setup/Initializing.scss'; @@ -25,22 +26,34 @@ export default function SetupInitializingForm() {
{isLoading ? ( <> -

It's time to make your accounting really simple!

+

+ +

- while we set up your account, please remember to verify your - account by clicking on the link we sent to yout registered email - address +

) : isError ? ( <> -

Something went wrong!

-

Please refresh the page

+

+ +

+

+ +

) : ( <> -

Waiting to redirect

-

Refresh the page if redirect not worked.

+

+ +

+

+ +

)}
diff --git a/client/src/containers/Setup/SetupLeftSection.js b/client/src/containers/Setup/SetupLeftSection.js index 82c69e816..3e39b9932 100644 --- a/client/src/containers/Setup/SetupLeftSection.js +++ b/client/src/containers/Setup/SetupLeftSection.js @@ -1,8 +1,8 @@ import React from 'react'; import { Icon, For } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; -import footerLinks from 'config/footerLinks'; +import { getFooterLinks } from 'config/footerLinks'; import { useAuthActions, useAuthOrganizationId } from 'hooks/state'; function FooterLinkItem({ title, link }) { @@ -20,6 +20,10 @@ export default function SetupLeftSection() { const { setLogout } = useAuthActions(); const organizationId = useAuthOrganizationId(); + // Retrieve the footer links. + const footerLinks = getFooterLinks(); + + // Handle logout link click. const onClickLogout = () => { setLogout(); }; @@ -42,7 +46,7 @@ export default function SetupLeftSection() {
- Oragnization ID: { organizationId }, + : { organizationId },
diff --git a/client/src/containers/Setup/SetupOrganizationForm.js b/client/src/containers/Setup/SetupOrganizationForm.js index dddf52535..4fa0a6a46 100644 --- a/client/src/containers/Setup/SetupOrganizationForm.js +++ b/client/src/containers/Setup/SetupOrganizationForm.js @@ -12,7 +12,7 @@ import { import { DateInput } from '@blueprintjs/datetime'; import classNames from 'classnames'; import { TimezonePicker } from '@blueprintjs/timezone'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { FieldRequiredHint, Col, Row, ListSelect } from 'components'; import { @@ -22,7 +22,7 @@ import { handleDateChange } from 'utils'; -import fiscalYearOptions from 'common/fiscalYearOptions'; +import { getFiscalYearOptions } from 'common/fiscalYearOptions'; import languages from 'common/languagesOptions'; import currencies from 'common/currencies'; @@ -31,6 +31,8 @@ import currencies from 'common/currencies'; * Setup organization form. */ export default function SetupOrganizationForm({ isSubmitting, values }) { + const fiscalYearOptions = getFiscalYearOptions(); + return (

@@ -96,7 +98,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) { > } + noResults={} />} popoverProps={{ minimal: true }} onItemSelect={(item) => { setFieldValue('baseCurrency', item.code); @@ -131,7 +133,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) { > } + noResults={} />} onItemSelect={(item) => { setFieldValue('language', item.value); }} @@ -163,7 +165,7 @@ export default function SetupOrganizationForm({ isSubmitting, values }) { > } + noResults={} />} selectedItem={value} selectedItemProp={'value'} textProp={'name'} diff --git a/client/src/containers/Setup/SetupOrganizationPage.js b/client/src/containers/Setup/SetupOrganizationPage.js index 240558f72..9f723dce6 100644 --- a/client/src/containers/Setup/SetupOrganizationPage.js +++ b/client/src/containers/Setup/SetupOrganizationPage.js @@ -2,7 +2,8 @@ import React from 'react'; import * as Yup from 'yup'; import { Formik } from 'formik'; import moment from 'moment'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import 'style/pages/Setup/Organization.scss'; @@ -24,29 +25,29 @@ function SetupOrganizationPage({ wizard, setOrganizationSetupCompleted, }) { - const { formatMessage } = useIntl(); + const { mutateAsync: organizationSetupMutate } = useOrganizationSetup(); // Validation schema. const validationSchema = Yup.object().shape({ organization_name: Yup.string() .required() - .label(formatMessage({ id: 'organization_name_' })), + .label(intl.get('organization_name_')), financialDateStart: Yup.date() .required() - .label(formatMessage({ id: 'date_start_' })), + .label(intl.get('date_start_')), baseCurrency: Yup.string() .required() - .label(formatMessage({ id: 'base_currency_' })), + .label(intl.get('base_currency_')), language: Yup.string() .required() - .label(formatMessage({ id: 'language' })), + .label(intl.get('language')), fiscalYear: Yup.string() .required() - .label(formatMessage({ id: 'fiscal_year_' })), + .label(intl.get('fiscal_year_')), timeZone: Yup.string() .required() - .label(formatMessage({ id: 'time_zone_' })), + .label(intl.get('time_zone_')), }); // Initial values. diff --git a/client/src/containers/Setup/SetupSubscription.js b/client/src/containers/Setup/SetupSubscription.js index de4b4eda3..9282b5626 100644 --- a/client/src/containers/Setup/SetupSubscription.js +++ b/client/src/containers/Setup/SetupSubscription.js @@ -1,25 +1,37 @@ import React from 'react'; import { Formik } from 'formik'; +import * as R from 'ramda'; import 'style/pages/Setup/Subscription.scss'; import SetupSubscriptionForm from './SetupSubscriptionForm'; -import { SubscriptionFormSchema } from './SubscriptionForm.schema'; +import { getSubscriptionFormSchema } from './SubscriptionForm.schema'; +import withSubscriptionPlansActions from '../Subscriptions/withSubscriptionPlansActions'; /** * Subscription step of wizard setup. */ -export default function SetupSubscription() { +function SetupSubscription({ + // #withSubscriptionPlansActions + initSubscriptionPlans +}) { + React.useEffect(() => { + initSubscriptionPlans(); + }, [ + initSubscriptionPlans + ]); + // Initial values. const initialValues = { - plan_slug: 'free', + plan_slug: 'starter', period: 'month', license_code: '', }; - // Handle form submit. const handleSubmit = () => {}; + const SubscriptionFormSchema = getSubscriptionFormSchema(); + return (
); -} \ No newline at end of file +} + +export default R.compose( + withSubscriptionPlansActions, +)(SetupSubscription); \ No newline at end of file diff --git a/client/src/containers/Setup/SubscriptionForm.schema.js b/client/src/containers/Setup/SubscriptionForm.schema.js index c5e055523..8dea27207 100644 --- a/client/src/containers/Setup/SubscriptionForm.schema.js +++ b/client/src/containers/Setup/SubscriptionForm.schema.js @@ -1,9 +1,9 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; -export const SubscriptionFormSchema = Yup.object().shape({ +export const getSubscriptionFormSchema = () => Yup.object().shape({ plan_slug: Yup.string() .required() - .label(formatMessage({ id: 'plan_slug' })), + .label(intl.get('plan_slug')), period: Yup.string().required(), }); \ No newline at end of file diff --git a/client/src/containers/Setup/WizardSetupSteps.js b/client/src/containers/Setup/WizardSetupSteps.js index ae4395c92..8b03b0ab0 100644 --- a/client/src/containers/Setup/WizardSetupSteps.js +++ b/client/src/containers/Setup/WizardSetupSteps.js @@ -1,24 +1,28 @@ import React from 'react'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; -import { registerWizardSteps } from 'common/registerWizard'; +import { getSetupWizardSteps } from 'common/registerWizard'; function WizardSetupStep({ label, isActive = false }) { return (
  • - + { label }

  • ); } +/** + * Setup wizard setups. + */ export default function WizardSetupSteps({ currentStep = 1 }) { + const setupWizardSetups = getSetupWizardSteps(); + return (
      - {registerWizardSteps.map((step, index) => ( + {setupWizardSetups.map((step, index) => ( { - changePageTitle(formatMessage({ id: 'billing' })); - }, [changePageTitle, formatMessage]); + changePageTitle(intl.get('billing')); + }, [changePageTitle]); const validationSchema = Yup.object().shape({ plan_slug: Yup.string() diff --git a/client/src/containers/Subscriptions/BillingPlan.js b/client/src/containers/Subscriptions/BillingPlan.js index cc6223f49..c15c450ae 100644 --- a/client/src/containers/Subscriptions/BillingPlan.js +++ b/client/src/containers/Subscriptions/BillingPlan.js @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import 'style/pages/Subscription/PlanRadio.scss'; @@ -32,7 +33,7 @@ export default function BillingPlan({ >
      - + {intl.get('plan_radio_name', { name: name })}
      @@ -54,4 +55,4 @@ export default function BillingPlan({
    ); -} \ No newline at end of file +} diff --git a/client/src/containers/Subscriptions/BillingPlansForm.js b/client/src/containers/Subscriptions/BillingPlansForm.js index eedbd87e8..3819d9286 100644 --- a/client/src/containers/Subscriptions/BillingPlansForm.js +++ b/client/src/containers/Subscriptions/BillingPlansForm.js @@ -1,5 +1,6 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import 'style/pages/Subscription/BillingPlans.scss'; @@ -14,15 +15,15 @@ export default function BillingPlansForm() { return (
    } + title={intl.get('select_a_plan')} description={} /> } + title={intl.get('choose_your_billing')} description={} /> } + title={intl.get('payment_methods')} description={} />
    diff --git a/client/src/containers/Subscriptions/BillingPlansInput.js b/client/src/containers/Subscriptions/BillingPlansInput.js index 540493cc8..abdb8d6a9 100644 --- a/client/src/containers/Subscriptions/BillingPlansInput.js +++ b/client/src/containers/Subscriptions/BillingPlansInput.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FastField } from 'formik'; +import { FastField, Field } from 'formik'; import BillingPlan from './BillingPlan'; import withPlans from './withPlans'; @@ -16,7 +16,7 @@ function BillingPlans({ plans, title, description, selectedOption }) {

    {description}

    - + {({ form: { setFieldValue }, field: { value } }) => (
    {plans.map((plan) => ( @@ -33,7 +33,7 @@ function BillingPlans({ plans, title, description, selectedOption }) { ))}
    )} -
    + ); } diff --git a/client/src/containers/Subscriptions/LicenseTab.js b/client/src/containers/Subscriptions/LicenseTab.js index 8143a4fab..6cd5ffc30 100644 --- a/client/src/containers/Subscriptions/LicenseTab.js +++ b/client/src/containers/Subscriptions/LicenseTab.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { Intent, Button } from '@blueprintjs/core'; @@ -28,8 +28,12 @@ function LicenseTab({ openDialog }) {

    -
    ); diff --git a/client/src/containers/Subscriptions/SubscriptionTabs.js b/client/src/containers/Subscriptions/SubscriptionTabs.js index f0ee09f0b..9280f2d09 100644 --- a/client/src/containers/Subscriptions/SubscriptionTabs.js +++ b/client/src/containers/Subscriptions/SubscriptionTabs.js @@ -1,6 +1,6 @@ import React from 'react'; import { Tabs, Tab } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import BillingTab from './BillingTab'; import LicenseTab from './LicenseTab'; @@ -8,18 +8,16 @@ import LicenseTab from './LicenseTab'; * Master billing tabs. */ export const MasterBillingTabs = ({ formik }) => { - const { formatMessage } = useIntl(); - return (
    } /> @@ -32,23 +30,21 @@ export const MasterBillingTabs = ({ formik }) => { * Payment methods tabs. */ export const PaymentMethodTabs = ({ formik }) => { - const { formatMessage } = useIntl(); - return (
    } /> diff --git a/client/src/containers/Subscriptions/withSubscriptionPlansActions.js b/client/src/containers/Subscriptions/withSubscriptionPlansActions.js new file mode 100644 index 000000000..0689ced84 --- /dev/null +++ b/client/src/containers/Subscriptions/withSubscriptionPlansActions.js @@ -0,0 +1,8 @@ +import { connect } from 'react-redux'; +import { initSubscriptionPlans } from 'store/plans/plans.actions'; + +export const mapDispatchToProps = (dispatch) => ({ + initSubscriptionPlans: () => dispatch(initSubscriptionPlans()), +}); + +export default connect(null, mapDispatchToProps); \ No newline at end of file diff --git a/client/src/containers/Vendors/VendorForm/VendorAttahmentTab.js b/client/src/containers/Vendors/VendorForm/VendorAttahmentTab.js index 71a4038e3..81001fd6c 100644 --- a/client/src/containers/Vendors/VendorForm/VendorAttahmentTab.js +++ b/client/src/containers/Vendors/VendorForm/VendorAttahmentTab.js @@ -1,5 +1,6 @@ import React from 'react'; import Dragzone from 'components/Dragzone'; +import { FormattedMessage as T } from 'components'; /** * Vendor Attahment Tab. @@ -11,7 +12,7 @@ function VendorAttahmentTab() { initialFiles={[]} onDrop={null} onDeleteFile={[]} - hint={'Attachments: Maxiumum size: 20MB'} + hint={} />
    ); diff --git a/client/src/containers/Vendors/VendorForm/VendorFinanicalPanelTab.js b/client/src/containers/Vendors/VendorForm/VendorFinanicalPanelTab.js index 922fcafbe..fb6f4aa1e 100644 --- a/client/src/containers/Vendors/VendorForm/VendorFinanicalPanelTab.js +++ b/client/src/containers/Vendors/VendorForm/VendorFinanicalPanelTab.js @@ -11,7 +11,7 @@ import { Row, Col, } from 'components'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import { momentFormatter, tansformDateValue, inputIntent } from 'utils'; import { useVendorFormContext } from './VendorFormProvider'; diff --git a/client/src/containers/Vendors/VendorForm/VendorFloatingActions.js b/client/src/containers/Vendors/VendorForm/VendorFloatingActions.js index 0bb5f2f14..1da3670da 100644 --- a/client/src/containers/Vendors/VendorForm/VendorFloatingActions.js +++ b/client/src/containers/Vendors/VendorForm/VendorFloatingActions.js @@ -9,7 +9,7 @@ import { Menu, MenuItem, } from '@blueprintjs/core'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; import { useFormikContext } from 'formik'; diff --git a/client/src/containers/Vendors/VendorForm/VendorForm.js b/client/src/containers/Vendors/VendorForm/VendorForm.js index 0a89fdcc6..ff046e631 100644 --- a/client/src/containers/Vendors/VendorForm/VendorForm.js +++ b/client/src/containers/Vendors/VendorForm/VendorForm.js @@ -2,11 +2,12 @@ import React, { useMemo, useEffect } from 'react'; import { Formik, Form } from 'formik'; import moment from 'moment'; import { Intent } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; +import { FormattedMessage as T } from 'components'; import AppToaster from 'components/AppToaster'; import { CreateVendorFormSchema, @@ -86,8 +87,6 @@ function VendorForm({ // History context. const history = useHistory(); - const { formatMessage } = useIntl(); - /** * Initial values in create and edit mode. */ @@ -111,11 +110,11 @@ function VendorForm({ const onSuccess = () => { AppToaster.show({ - message: formatMessage({ - id: isNewMode + message: intl.get( + isNewMode ? 'the_vendor_has_been_created_successfully' : 'the_item_vendor_has_been_edited_successfully', - }), + ), intent: Intent.SUCCESS, }); setSubmitPayload(false); diff --git a/client/src/containers/Vendors/VendorForm/VendorForm.schema.js b/client/src/containers/Vendors/VendorForm/VendorForm.schema.js index 1edbde25e..fa2fa0892 100644 --- a/client/src/containers/Vendors/VendorForm/VendorForm.schema.js +++ b/client/src/containers/Vendors/VendorForm/VendorForm.schema.js @@ -1,5 +1,5 @@ import * as Yup from 'yup'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; const Schema = Yup.object().shape({ salutation: Yup.string().trim(), @@ -9,7 +9,7 @@ const Schema = Yup.object().shape({ display_name: Yup.string() .trim() .required() - .label(formatMessage({ id: 'display_name_' })), + .label(intl.get('display_name_')), email: Yup.string().email().nullable(), work_phone: Yup.number(), diff --git a/client/src/containers/Vendors/VendorForm/VendorFormAfterPrimarySection.js b/client/src/containers/Vendors/VendorForm/VendorFormAfterPrimarySection.js index 9e51bd550..52f67d598 100644 --- a/client/src/containers/Vendors/VendorForm/VendorFormAfterPrimarySection.js +++ b/client/src/containers/Vendors/VendorForm/VendorFormAfterPrimarySection.js @@ -1,13 +1,16 @@ import React from 'react'; import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core'; import { FastField, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { inputIntent } from 'utils'; /** * Vendor form after primary section. */ function VendorFormAfterPrimarySection() { + + return (
    {/*------------ Vendor email -----------*/} @@ -36,7 +39,7 @@ function VendorFormAfterPrimarySection() { {({ field, meta: { error, touched } }) => ( )} @@ -45,7 +48,7 @@ function VendorFormAfterPrimarySection() { {({ field, meta: { error, touched } }) => ( )} diff --git a/client/src/containers/Vendors/VendorForm/VendorFormPrimarySection.js b/client/src/containers/Vendors/VendorForm/VendorFormPrimarySection.js index c1bf1830e..b39e30d13 100644 --- a/client/src/containers/Vendors/VendorForm/VendorFormPrimarySection.js +++ b/client/src/containers/Vendors/VendorForm/VendorFormPrimarySection.js @@ -2,7 +2,8 @@ import React from 'react'; import classNames from 'classnames'; import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core'; import { FastField, Field, ErrorMessage } from 'formik'; -import { FormattedMessage as T } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import { Hint, FieldRequiredHint, @@ -20,6 +21,7 @@ import { useAutofocus } from 'hooks'; */ function VendorFormPrimarySection() { const firstNameFieldRef = useAutofocus(); + return (
    @@ -51,7 +53,7 @@ function VendorFormPrimarySection() { {({ field, meta: { error, touched } }) => ( (firstNameFieldRef.current = ref)} @@ -63,7 +65,7 @@ function VendorFormPrimarySection() { {({ field, meta: { error, touched } }) => ( } /> } /> } /> } /> diff --git a/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js b/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js index a65dbbee2..01fe745d3 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorActionsBar.js @@ -9,7 +9,8 @@ import { Position, PopoverInteractionKind, } from '@blueprintjs/core'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import classNames from 'classnames'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; @@ -31,7 +32,7 @@ function VendorActionsBar({ setVendorsTableState, }) { const history = useHistory(); - const { formatMessage } = useIntl(); + // Vendors list context. const { vendorsViews } = useVendorsListContext(); @@ -73,7 +74,7 @@ function VendorActionsBar({ true ? ( ) : ( - `${9} ${formatMessage({ id: 'filters_applied' })}` + `${9} ${intl.get('filters_applied')}` ) } icon={} diff --git a/client/src/containers/Vendors/VendorsLanding/VendorsEmptyStatus.js b/client/src/containers/Vendors/VendorsLanding/VendorsEmptyStatus.js index ff2d11247..5ce0cdc33 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorsEmptyStatus.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorsEmptyStatus.js @@ -2,17 +2,17 @@ import React from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import { EmptyStatus } from 'components'; +import { FormattedMessage as T } from 'components'; export default function VendorsEmptyStatus() { const history = useHistory(); return ( } description={

    - Here a list of your organization products and services, to be used - when you create invoices or bills to your customers or vendors. +

    } action={ @@ -24,11 +24,11 @@ export default function VendorsEmptyStatus() { history.push('/vendors/new'); }} > - New vendor + } diff --git a/client/src/containers/Vendors/VendorsLanding/components.js b/client/src/containers/Vendors/VendorsLanding/components.js index d9c2aae25..75b64c620 100644 --- a/client/src/containers/Vendors/VendorsLanding/components.js +++ b/client/src/containers/Vendors/VendorsLanding/components.js @@ -8,7 +8,7 @@ import { Position, Intent, } from '@blueprintjs/core'; -import { useIntl } from 'react-intl'; +import intl from 'react-intl-universal'; import { Icon, Money } from 'components'; import { safeCallback, firstLettersArgs } from 'utils'; @@ -19,28 +19,28 @@ export function ActionsMenu({ row: { original }, payload: { onEdit, onDelete, onDuplicate }, }) { - const { formatMessage } = useIntl(); + return ( } - text={formatMessage({ id: 'view_details' })} + text={intl.get('view_details')} /> } - text={formatMessage({ id: 'edit_vendor' })} + text={intl.get('edit_vendor')} onClick={safeCallback(onEdit, original)} /> } - text={formatMessage({ id: 'duplicate' })} + text={intl.get('duplicate')} onClick={safeCallback(onDuplicate, original)} /> } - text={formatMessage({ id: 'delete_vendor' })} + text={intl.get('delete_vendor')} intent={Intent.DANGER} onClick={safeCallback(onDelete, original)} /> @@ -87,7 +87,7 @@ export function BalanceAccessor({ closing_balance, currency_code }) { * Retrieve the vendors table columns. */ export function useVendorsTableColumns() { - const { formatMessage } = useIntl(); + return React.useMemo( () => [ @@ -102,33 +102,33 @@ export function useVendorsTableColumns() { }, { id: 'display_name', - Header: formatMessage({ id: 'display_name' }), + Header: intl.get('display_name'), accessor: 'display_name', className: 'display_name', width: 150, }, { id: 'company_name', - Header: formatMessage({ id: 'company_name' }), + Header: intl.get('company_name'), accessor: 'company_name', className: 'company_name', width: 150, }, { id: 'work_phone', - Header: formatMessage({ id: 'work_phone' }), + Header: intl.get('work_phone'), accessor: PhoneNumberAccessor, className: 'work_phone', width: 100, }, { id: 'balance', - Header: formatMessage({ id: 'receivable_balance' }), + Header: intl.get('receivable_balance'), accessor: BalanceAccessor, className: 'receivable_balance', width: 100, } ], - [formatMessage], + [], ); } diff --git a/client/src/containers/Vendors/VendorsLanding/utils.js b/client/src/containers/Vendors/VendorsLanding/utils.js index f647c6b0d..b1fbe025c 100644 --- a/client/src/containers/Vendors/VendorsLanding/utils.js +++ b/client/src/containers/Vendors/VendorsLanding/utils.js @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; @@ -7,9 +7,7 @@ import { AppToaster } from 'components'; export const transformErrors = useCallback((errors) => { if (errors.some((e) => e.type === 'VENDOR.HAS.BILLS')) { AppToaster.show({ - message: formatMessage({ - id: 'vendor_has_bills', - }), + message: intl.get('vendor_has_bills'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/Vendors/utils.js b/client/src/containers/Vendors/utils.js index c95f91654..e87865068 100644 --- a/client/src/containers/Vendors/utils.js +++ b/client/src/containers/Vendors/utils.js @@ -1,23 +1,18 @@ import React from 'react'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; -import { formatMessage } from 'services/intl'; +import intl from 'react-intl-universal'; export const transformErrors = (errors) => { if (errors.find((error) => error.type === 'VENDOR.HAS.ASSOCIATED.BILLS')) { AppToaster.show({ - message: formatMessage({ - id: 'cannot_delete_vendor_that_has_associated_purchase_bills', - }), + message: intl.get('cannot_delete_vendor_that_has_associated_purchase_bills'), intent: Intent.DANGER, }); } if (errors.find((error) => error.type === 'VENDOR_HAS_TRANSACTIONS')) { AppToaster.show({ - message: formatMessage({ - id: - 'this_vendor_cannot_be_deleted_as_it_is_associated_with_transactions', - }), + message: intl.get('this_vendor_cannot_be_deleted_as_it_is_associated_with_transactions'), intent: Intent.DANGER, }); } diff --git a/client/src/containers/Views/ViewForm.js b/client/src/containers/Views/ViewForm.js index 58fa48c93..41197bc1d 100644 --- a/client/src/containers/Views/ViewForm.js +++ b/client/src/containers/Views/ViewForm.js @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useFormik } from 'formik'; -import { FormattedMessage as T, useIntl } from 'react-intl'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; import {useHistory } from 'react-router-dom'; import { @@ -339,7 +340,7 @@ function ViewForm({ intent={hasError(`roles[${index}].value`) && Intent.DANGER} > @@ -370,7 +371,7 @@ function ViewForm({ Available Columns @@ -449,7 +450,7 @@ function ViewForm({
    Selected Columns
    diff --git a/client/src/containers/Views/ViewFormPage.js b/client/src/containers/Views/ViewFormPage.js index fb7043c66..0cc4181d2 100644 --- a/client/src/containers/Views/ViewFormPage.js +++ b/client/src/containers/Views/ViewFormPage.js @@ -2,7 +2,7 @@ import React, {useEffect, useState, useCallback} from 'react'; import { useAsync } from 'react-use'; import { useParams } from 'react-router-dom'; import { Intent, Alert } from '@blueprintjs/core'; -import { FormattedMessage as T, FormattedHTMLMessage, useIntl } from 'react-intl'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; @@ -33,7 +33,7 @@ function ViewFormPage({ const { resource_slug: resourceSlug, view_id: viewId } = useParams(); const [stateDeleteView, setStateDeleteView] = useState(null); - const { formatMessage } = useIntl(); + const fetchHook = useAsync(async () => { return Promise.all([ @@ -51,14 +51,14 @@ function ViewFormPage({ useEffect(() => { if (viewId) { - changePageTitle(formatMessage({id:'edit_custom_view'})); + changePageTitle(intl.get('edit_custom_view')); } else { - changePageTitle(formatMessage({id:'new_custom_view'})); + changePageTitle(intl.get('new_custom_view')); } return () => { changePageTitle(''); }; - }, [viewId, changePageTitle,formatMessage]); + }, [viewId, changePageTitle]); // Handle delete view button click. @@ -76,9 +76,7 @@ function ViewFormPage({ requestDeleteView(stateDeleteView.id).then((response) => { setStateDeleteView(null); AppToaster.show({ - message: formatMessage({ - id: 'the_custom_view_has_been_deleted_successfully', - }), + message: intl.get('the_custom_view_has_been_deleted_successfully'), intent: Intent.SUCCESS, }); }) diff --git a/client/src/hooks/query/financialReports.js b/client/src/hooks/query/financialReports.js index 4989099ca..1826269c2 100644 --- a/client/src/hooks/query/financialReports.js +++ b/client/src/hooks/query/financialReports.js @@ -402,3 +402,67 @@ export function useVendorsTransactionsReport(query, props) { }, ); } + +/** + * Retrieve cash flow statement report. + */ +export function useCashFlowStatementReport(query, props) { + return useRequestQuery( + [t.FINANCIAL_REPORT, t.CASH_FLOW_STATEMENT, query], + { + method: 'get', + url: '/financial_statements/cash-flow', + params: query, + headers: { + Accept: 'application/json+table', + }, + }, + { + select: (res) => ({ + columns: res.data.table.columns, + query: res.data.query, + meta: res.data.meta, + tableRows: res.data.table.data, + }), + defaultData: { + tableRows: [], + columns: [], + query: {}, + meta: {}, + }, + ...props, + }, + ); +} + +/** + * Retrieve inventory item detail report. + */ +export function useInventoryItemDetailsReport(query, props) { + return useRequestQuery( + [t.FINANCIAL_REPORT, t.INVENTORY_ITEM_DETAILS, query], + { + method: 'get', + url: '/financial_statements/inventory-item-details', + params: query, + headers: { + Accept: 'application/json+table', + }, + }, + { + select: (res) => ({ + columns: res.data.table.columns, + query: res.data.query, + meta: res.data.meta, + tableRows: res.data.table.data, + }), + defaultData: { + tableRows: [], + columns: [], + query: {}, + meta: {}, + }, + ...props, + }, + ); +} diff --git a/client/src/hooks/query/types.js b/client/src/hooks/query/types.js index 61db1c84e..c2bfb67d4 100644 --- a/client/src/hooks/query/types.js +++ b/client/src/hooks/query/types.js @@ -20,7 +20,9 @@ const FINANCIAL_REPORTS = { CUSTOMERS_BALANCE_SUMMARY: 'CUSTOMERS_BALANCE_SUMMARY', SALES_BY_ITEMS: 'SALES_BY_ITEMS', PURCHASES_BY_ITEMS: 'PURCHASES_BY_ITEMS', - INVENTORY_VALUATION: 'INVENTORY_VALUATION' + INVENTORY_VALUATION: 'INVENTORY_VALUATION', + CASH_FLOW_STATEMENT: 'CASH_FLOW_STATEMENT', + INVENTORY_ITEM_DETAILS:'INVENTORY_ITEM_DETAILS' }; const BILLS = { diff --git a/client/src/hooks/useRequest.js b/client/src/hooks/useRequest.js index b71020bfc..5993d7f67 100644 --- a/client/src/hooks/useRequest.js +++ b/client/src/hooks/useRequest.js @@ -24,7 +24,7 @@ export default function useApiRequest() { // Request interceptors. instance.interceptors.request.use( (request) => { - const locale = 'en'; + const locale = 'ar'; if (token) { request.headers.common['X-Access-Token'] = token; diff --git a/client/src/lang/ar-ly/index.json b/client/src/lang/ar-ly/index.json new file mode 100644 index 000000000..12d3a287f --- /dev/null +++ b/client/src/lang/ar-ly/index.json @@ -0,0 +1,1124 @@ +{ + "hello_world": "مرحبا بالعالم", + "email_or_phone_number": "البريد الإلكتروني أو رقم الهاتف", + "password": "كلمه السر", + "login": "تسجيل الدخول", + "invalid_email_or_phone_number": "البريد الإلكتروني أو رقم الهاتف غير صحيح.", + "required": "مطلوب", + "reset_password": "إعادة تعيين كلمة المرور", + "the_user_has_been_suspended_from_admin": "تم تعليق المستخدم من المسؤول.", + "email_and_password_entered_did_not_match": "البريد الإلكتروني وكلمة المرور اللذان أدخلتهما غير متطابقان.", + "field_name_must_be_number": "field_name_must_be_number", + "name": "اسم", + "quick_find": "البحث السريع", + "reference": "المرجعي #", + "date": "تاريخ", + "description": "الوصف", + "from_date": "من التاريخ", + "to_date": "الى تاريخ", + "report_date_range": "تقرير نطاق التاريخ", + "log_in": "تسجيل الدخول", + "forget_my_password": "نسيت كلمة المرور الخاصة بي", + "keep_me_logged_in": "تذكرني", + "create_an_account": "إنشاء حساب", + "need_bigcapital_account": "تحتاج إلى حساب Bigcapital؟", + "show": "عرض", + "hide": "إخفاء", + "an_unexpected_error_occurred": "حدث خطأ غير متوقع", + "welcome_to_bigcapital": "مرحبا بكم في Bigcapital", + "enter_your_personal_information": "أدخل بياناتك الشخصية", + "first_name": "الاسم الأول", + "last_name": "اللقب", + "phone_number": "رقم الهاتف", + "you_email_address_is": "عنوان بريدك الإلكتروني هو", + "you_will_use_this_address_to_sign_in_to_bigcapital": "سوف تستخدم هذا العنوان لتسجيل الدخول إلى Bigcapital.", + "signing_in_or_creating": "من خلال تسجيل الدخول أو إنشاء حساب ، فإنك توافق على", + "and": "و", + "create_account": "إنشاء حساب", + "success": "نجح", + "register_a_new_organization": "تسجيل منشأة جديدة.", + "you_have_a_bigcapital_account": "لديك حساب Bigcapital ", + "organization_name": "اسم منشأة", + "email": "بريد إلكتروني", + "register": "تسجيل", + "password_successfully_updated": "تم تحديث كلمة المرور لحسابك بنجاح.", + "choose_a_new_password": "اختر كلمة مرور جديدة", + "you_remembered_your_password": "هل تذكرت كلمة المرور الخاصة بك؟", + "new_password": "كلمة السر الجديدة", + "submit_new_password": "ادخال كلمة المرور الجديدة", + "you_can_t_login": "لا يمكنك تسجيل الدخول؟", + "we_ll_send_a_recovery_link_to_your_email": "سنرسل لك رابط استرداد كلمة المرور عبر بريدك الإلكتروني.", + "send_reset_password_mail": "إرسال بريد استرداد كلمة المرور", + "return_to_log_in": "العودة لتسجيل الدخول", + "sub_account": "حساب فرعي؟", + "account_type": "نوع الحساب", + "account_name": "أسم الحساب", + "account_code": "رمز الحساب", + "parent_account": "الحساب الأصل", + "edit": "تعديل", + "submit": "ادخال", + "close": "اغلاق", + "edit_account": "تعديل الحساب", + "new_account": "حساب جديد", + "edit_currency": "تعديل العملة", + "delete_currency": "حذف العملة", + "new_currency": "عملة جديدة", + "currency_name": "اسم العملة", + "currency_code": "رمز العملة", + "select_currency_code": "تعديل رمز العملة", + "edit_exchange_rate": "تعديل سعر الصرف", + "new_exchange_rate": "سعر صرف الجديد", + "delete_exchange_rate": "حذف سعر الصرف", + "exchange_rate": "سعر الصرف", + "edit_category": "تعديل التصنيف", + "delete_category": "حذف التصنيف", + "new_category": "تصنيف جديدة", + "category_name": "اسم التصنيف", + "parent_category": "التصنيف الرئيسي", + "new": "جديد", + "invite_user": "دعوة مستخدم", + "your_access_to_your_team": "سيتلقى زميلك في الفريق رسالة بريد إلكتروني تتيح له الوصول إلى فريقك.", + "invite": "التصنيف", + "count": "العدد", + "item_type": "نوع المنتج", + "item_name": "اسم المنتج", + "category": "التصنيف", + "account": "الحساب", + "sales_information": "بيانات البيع", + "purchase_information": "بيانات الشراء", + "selling_price": "سعر البيع", + "cost_price": "سعر التكلفة", + "inventory_information": "بيانات المخزون", + "inventory_account": "حساب المخزون", + "opening_quantity": "كمية الافتتاحية", + "opening_cost": "التكلفة الافتتاحية", + "save": "حفظ", + "save_as_draft": "حفظ كمسودة", + "active": "تفعيل", + "draft": "مسودة", + "published": "نشرت", + "new_item": "منتج جديد", + "cost_price_": "سعر التكلفة", + "sell_price_": "سعر البيع", + "table_views": "عروض الجدول", + "delete": "حذف", + "delete_count": "حذف ({عدد})", + "import": "استيراد", + "export": "تصدير", + "filter": "فلتر", + "view_details": "عرض التفاصيل", + "edit_item": "تعديل المنتج", + "delete_item": "حذف المنتج", + "sell_price": "سعر البيع", + "cancel": "إلغاء", + "move_to_trash": "نقل الى سلة المحذوفات", + "save_new": "حفظ & جديد", + "journal_number": "رقم القيد", + "credit_currency": "دائن ({currency})", + "debit_currency": "مدين ({currency})", + "note": "ملاحظة", + "new_lines": "سطر جديد", + "clear_all_lines": "امسح كل الاسطر", + "new_journal": "قيد جديد", + "publish_journal": "نشر القيد", + "edit_journal": "تعديل القيد", + "delete_journal": "حذف القيد", + "amount": "القيمة", + "journal_no": "رقم القيد", + "status": "حالة", + "transaction_type": "نوع المعاملة", + "created_at": "أنشئت في", + "archive": "أرشيف", + "inactivate": "تعطيل", + "activate": "تفعيل", + "inactivate_account": "تعطيل الحساب", + "activate_account": "تفعيل حساب", + "delete_account": "حذف الحساب", + "code": "الكود", + "type": "النوع", + "normal": "طبيعة", + "balance": "الرصيد", + "something_wrong": "هناك خطب ما", + "filters": "الفلتر", + "add_order": "اضافة طلب", + "expense_account": "حساب المصاريف", + "payment_account": "حساب الدفع", + "new_expense": "مصاريف جديدة", + "bulk_update": "تحديث بالجملة", + "all_accounts": "جميع الحسابات", + "go_to_bigcapital_com": "→ اذهب إلى bigcapital.com", + "currency": "عملة", + "new_conditional": "شرط جديد +", + "chart_of_accounts": "شجرة الحسابات", + "exchange_rate_details": "تفاصيل سعر الصرف", + "exchange_rates_list": "قائمة أسعار الصرف", + "manual_journals": "قيود اليدوية", + "edit_expense_details": "تعديل المصروف", + "expenses_list": "قائمة المصروفات", + "edit_category_details": "تعديل التصنيف", + "category_list": "قائمة التصنيفات", + "edit_item_details": "تعديل المنتج", + "items_list": "قائمة المنتجات", + "edit_custom_view": "تعديل العرض المخصص", + "new_custom_view": "اضافة عرض مخصص", + "view_name": "عرض الاسم", + "item": "المنتج", + "service_has_been_created_successfully": "تم إنشاء {service} {name} بنجاح.", + "service_has_been_edited_successfully": "تم تعديل {service} {name} بنجاح.", + "you_are_about_permanently_delete_this_journal": "أنت على وشك حذف هذا القيد بشكل دائم وجميع معاملاته على الحسابات.

    إذا لم تكن متأكدًا ، يمكنك أرشفة هذا القيد بدلاً من ذلك.", + "once_delete_these_accounts_you_will_not_able_restore_them": "بمجرد حذف هذه الحسابات ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "once_delete_these_service_you_will_not_able_restore_it": "بمجرد حذف هذه {service} ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذه {service}؟", + "you_could_not_delete_predefined_accounts": "لا يمكنك حذف الحسابات المحددة مسبقًا.", + "cannot_delete_account_has_associated_transactions": "لا يمكنك حذف حساب لديه معاملات.", + "the_account_has_been_successfully_inactivated": "تم تعطيل الحساب بنجاح.", + "the_account_has_been_successfully_activated": "تم تفعيل الحساب بنجاح.", + "the_account_has_been_successfully_deleted": "تم حذف الحساب بنجاح.", + "the_accounts_has_been_successfully_deleted": "تم حذف الحسابات بنجاح.", + "are_sure_to_inactive_this_account": "هل أنت متأكد أنك تريد إلغاء تنشيط هذا الحساب؟ ستكون قادرًا على تنشيطه لاحقًا", + "are_sure_to_inactive_this_accounts": "هل أنت متأكد أنك تريد إلغاء تنشيط هذه الحسابات؟ ستكون قادرًا على تنشيطه لاحقًا", + "are_sure_to_activate_this_account": "هل أنت متأكد أنك تريد تفعيل هذا الحساب؟ ستتمكن من تعطيله لاحقًا", + "are_sure_to_activate_this_accounts": "هل أنت متأكد أنك تريد تفعيل هذه الحسابات؟ ستتمكن من تعطيله لاحقًا", + "once_delete_this_account_you_will_able_to_restore_it": "بمجرد حذف هذا الحساب ، لن تتمكن من استعادته لاحقًا. هل تريد بالتأكيد حذف هذا الحساب؟

    إذا لم تكن متأكدًا ، يمكنك إلغاء تنشيط هذا الحساب بدلاً من ذلك.", + "the_journal_has_been_created_successfully": "تم إنشاء القيد {number}# بنجاح.", + "the_journal_has_been_edited_successfully": "تم تعديل االقيد {number}# بنجاح.", + "the_journal_has_been_deleted_successfully": "تم حذف القيد بنجاح", + "the_manual_journal_has_been_published": "تم نشر القيد اليدوي.", + "the_journals_has_been_deleted_successfully": "تم حذف المجلات بنجاح ", + "credit": "دائن", + "debit": "مدين", + "once_delete_this_item_you_will_able_to_restore_it": "بمجرد حذف هذا المنتج ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد الحذف؟

    إذا لم تكن متأكدًا ، يمكنك إلغاء تنشيطه بدلاً من ذلك.", + "the_item_has_been_deleted_successfully": "تم حذف المنتج بنجاح.", + "the_item_has_been_created_successfully": "تم اضافة المنتج بنجاح.", + "the_item_has_been_edited_successfully": "تم تعديل المنتج رقم {number}# بنجاح", + "the_item_category_has_been_created_successfully": "تم إنشاء تصنيف المنتجات بنجاح.", + "the_item_category_has_been_edited_successfully": "تم تعديل تصنيف المنتجات بنجاح.", + "the_item_category_has_been_deleted_successfully": "تم حذف تصنيف المنتجات بنجاح.", + "once_delete_these_item_categories_you_will_not_able_restore_them": "بمجرد حذف هذه التصنيفات ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "once_delete_these_views_you_will_not_able_restore_them": "بمجرد حذف العرض المخصص ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف هذا العرض؟", + "the_custom_view_has_been_deleted_successfully": "تم حذف العرض المخصص بنجاح.", + "teammate_invited_to_organization_account": "تمت دعوة زميلك في الفريق إلى حساب المنشأة.", + "select_account_type": "حدد نوع الحساب", + "menu": "القائمة", + "table": "الجدول", + "logout": "تسجيل خروج", + "select_payment_account": "حدد حساب الدفع", + "select_expense_account": "حدد حساب المصاريف", + "or": "أو", + "comparator": "المقارن", + "equals": "يساوي", + "not_equal": "ليس متساوي", + "contain": "يحتوي", + "not_contain": "لا يحتوي", + "cash": "النقدية", + "accrual": "الاستحقاق", + "from": "من", + "to": "إلي", + "accounting_basis": "الأساس المحاسبي:", + "general": "عام", + "users": "المستخدمون", + "currencies": "العملات", + "accountant": "محاسب", + "accounts": "الحسابات", + "homepage": "الصفحة الرئيسية", + "items": "المنتجات", + "financial": "الأمور المالية", + "accounts_chart": "شجرة الحسابات", + "manual_journal": "قيد يدوي", + "make_journal": "اضافة قيد يدوي", + "banking": "الخدمات المصرفية", + "sales": "المبيعات", + "purchases": "المشتريات", + "financial_reports": "تقارير مالية", + "all_financial_reports": "كل التقارير المالية", + "balance_sheet": "الميزانية العمومية", + "trial_balance_sheet": "ميزان المراجعة", + "journal": "اليومية العامة", + "general_ledger": "دفتر الأستاذ العام", + "general_ledger_sheet": "ورقة دفتر الأستاذ العام", + "profit_loss_sheet": "قائمة الدخل", + "expenses": "المصاريف", + "new_expenses": "مصاريف جديدة", + "preferences": "التفضيلات", + "auditing_system": "نظام المراجعة", + "all": "الجميع", + "organization": "منشأة.", + "check_your_email_for_a_link_to_reset": "تحقق من بريدك الإلكتروني بحثًا عن رابط لإعادة تعيين كلمة مرورك. إذا لم يصلك في غضون بضع دقائق ، فتحقق من مجلد الرسائل غير المرغوب فيها.", + "we_couldn_t_find_your_account_with_that_email": "لم نتمكن من العثور على حسابك بهذا البريد الإلكتروني.", + "select_parent_account": "حدد الحساب الرئيسي", + "the_exchange_rate_has_been_edited_successfully": "تم تعديل سعر الصرف بنجاح", + "the_exchange_rate_has_been_created_successfully": "تم إنشاء سعر الصرف بنجاح", + "the_user_details_has_been_updated": "تم تحديث تفاصيل المستخدم", + "filters_applied": "تم تطبيق الفلتر", + "select_item_type": "حدد نوع المنتج", + "service": "خدمة", + "inventory": "المخزون", + "non_inventory": "غير مخزون", + "select_category": "اختر التصنيف", + "select_account": "اختار حساب", + "custom_fields": "الحقول المخصصة", + "organization_industry": "مجال المنظمة", + "business_location": "موقع العمل", + "base_currency": "العملة الأساسية", + "fiscal_year": "السنة المالية", + "language": "لغة", + "time_zone": "المنطقة الزمنية", + "date_format": "صيغة التاريخ", + "edit_user": "تعديل المستخدم", + "edit_invite": "تعديل دعوة", + "inactivate_user": "تعطيل المستخدم", + "activate_user": "تفعيل المستخدم", + "delete_user": "حذف المستخدم", + "full_name": "الاسم بالكامل", + "the_user_has_been_inactivated_successfully": "تم تعطيل المستخدم بنجاح.", + "the_user_has_been_deleted_successfully": "تم حذف المستخدم بنجاح.", + "customize_report": "تخصيص التقرير", + "print": "طباعة", + "accounts_with_zero_balance": "حسابات ذات رصيد صفر", + "all_transactions": "كل الحركات المالية", + "filter_accounts": "تصفية الحسابات", + "calculate_report": "حساب التقرير", + "total": "إجمالي", + "specific_accounts": "حسابات محددة", + "trans_num": "عبر. NUM", + "journal_sheet": "دفتر اليومية العامة", + "run_report": "حساب التقرير", + "num": "رقم.", + "inviting": "دعوة", + "acc_code": "رمز الحساب", + "display_report_columns": "عرض أعمدة التقرير", + "select_display_columns_by": "حدد أعمدة العرض حسب ...", + "credit_and_debit_not_equal": "المدين والدائن غير متساويين في القيمة.", + "the_currency_has_been_edited_successfully": "تم تعديل العملة بنجاح", + "the_currency_has_been_created_successfully": "تم اضافة عملة بنجاح", + "the_currency_has_been_deleted_successfully": "تم حذف العملة بنجاح", + "once_delete_this_currency_you_will_able_to_restore_it": "بمجرد حذف هذه العملة ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذا البند؟", + "once_delete_this_exchange_rate_you_will_able_to_restore_it": "بمجرد حذف سعر الصرف هذا ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف سعر الصرف هذا؟", + "once_delete_these_exchange_rates_you_will_not_able_restore_them": "بمجرد حذف أسعار الصرف هذه ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "once_delete_this_item_category_you_will_able_to_restore_it": "بمجرد حذف هذه الفئة ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذا البند؟", + "select_business_location": "حدد موقع العمل", + "select_base_currency": "حدد العملة الأساسية", + "select_fiscal_year": "حدد السنة المالية", + "select_language": "اختار اللغة", + "select_date_format": "حدد تنسيق التاريخ", + "select_time_zone": "اختر المجال الزمني", + "select_currency": "اختر العملة", + "select_parent_category": "حدد تصنيف الأصل", + "the_options_has_been_created_successfully": "تم إنشاء الخيارات بنجاح", + "there_is_exchange_rate_in_this_date_with_the_same_currency": "يوجد سعر صرف في هذا التاريخ بنفس العملة.", + "the_exchange_rates_has_been_deleted_successfully": "تم حذف أسعار الصرف بنجاح", + "once_delete_this_expense_you_will_able_to_restore_it": "بمجرد حذف هذا المصروف ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف؟", + "january": "كانون الثاني", + "february": "شهر فبراير", + "march": "مارس", + "april": "أبريل", + "may": "مايو", + "june": "يونيو", + "july": "تموز", + "august": "أغسطس", + "september": "شهر سبتمبر", + "october": "اكتوبر", + "november": "شهر نوفمبر", + "december": "ديسمبر", + "expense_account_id": "حساب المصاريف", + "payment_account_id": "حساب الدفع", + "currency_code_": "رمز العملة", + "publish": "ينشر", + "exchange_rate_": "سعر الصرف", + "journal_number_": "رقم القيد", + "first_name_": "الاسم الأول", + "last_name_": "الكنية", + "phone_number_": "رقم الهاتف", + "organization_name_": "اسم منشأة", + "confirm_password": "تأكيد كلمة المرور", + "crediential": "البريد الإلكتروني أو رقم الهاتف", + "account_type_id": "نوع الحساب", + "account_name_": "أسم الحساب", + "currency_name_": "اسم العملة", + "cost_account_id": "حساب التكلفة", + "sell_account_id": "حساب البيع", + "item_type_": "نوع المنتج", + "item_name_": "اسم المنتج", + "organization_industry_": "صناعة المنشأة", + "base_currency_": "العملة الأساسية", + "date_format_": "صيغة التاريخ", + "category_name_": "اسم التصنيف", + "sell_account_": "بيع حساب", + "cost_account_": "حساب التكلفة", + "inventory_account_": "حساب المخزون", + "view_name_": "عرض الاسم", + "time_zone_": "وحدة زمنية", + "location": "موقع", + "the_items_has_been_deleted_successfully": "تم حذف المنتجات بنجاح.", + "once_delete_these_items_you_will_not_able_restore_them": "بمجرد حذف هذه الاصناف ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "ops_something_went_wrong": "هناك خطأ ما! حاول مرة اخرى.", + "session_expired": "انتهت الجلسة!", + "this_report_does_not_contain_any_data_between_date_period": "لا يحتوي هذا التقرير على أي بيانات بين فترات التاريخ المحددة.", + "welcome_organization_account_has_been_created": "👋 مرحبًا ، تم إنشاء حساب للمنشأة ، قم بتسجيل الدخول الآن!", + "the_phone_number_already_used_in_another_account": "رقم الهاتف مستخدم بالفعل في حساب آخر", + "the_email_already_used_in_another_account": "البريد الإلكتروني مستخدم بالفعل في حساب آخر", + "hide_filter": "إخفاء الترشيح", + "show_filter": "عرض عامل التصفية", + "new_role": "دور جديد", + "quick_new": "انشاء سريع", + "help": "مساعدة", + "organization_id": "معرف المنشأة", + "beneficiary": "المستفيد", + "payment_date": "تاريخ الدفع", + "ref_no": "رقم الاشاري", + "payment_account_": "حساب الدفع", + "expense_category": "تصنيف المصاريف", + "total_currency": "الإجمالي ({currency})", + "amount_currency": "العملة كمية})", + "publish_expense": "نشر المصروف", + "edit_expense": "تعديل المصروف", + "delete_expense": "حذف المصروف", + "full_amount": "المبلغ بالكامل", + "payment_date_": "تاريخ الدفع", + "the_expense_has_been_created_successfully": "تم اضافة مصروف {number}# بنجاح.", + "the_expense_has_been_edited_successfully": "تم تعديل المصروف {number}# بنجاح.", + "the_expense_has_been_deleted_successfully": "تم حذف المصروف بنجاح", + "the_expenses_have_been_deleted_successfully": "تم حذف المصاريف بنجاح", + "once_delete_these_expenses_you_will_not_able_restore_them": "بمجرد حذف هذه المصاريف ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "the_expense_has_been_published": "تم نشر المصروف", + "select_customer": "حدد الزبون", + "total_amount_equals_zero": "المبلغ الإجمالي يساوي صفر", + "value": "القيمة", + "you_reached_conditions_limit": "لقد وصلت إلى حد المشروط.", + "customer_name": "اسم الزبون", + "as_date": "كتاريخ", + "aging_before_days": "الايام قبل الاعمار", + "aging_periods": "فترات الاعمار ", + "name_": "اسم", + "as": "مثل", + "receivable_aging_summary": "ملخص اعمار الذمم المدينة", + "AR_Aging_Summary": "ملخص اعمار الديون للذمم المدينة", + "AP_Aging_Summary": "ملخص اعمار الديون للذمم الدائنة", + "customers": "العملاء", + "new_customers": "زبائن الجدد", + "customer_type_": "نوع الزبون", + "display_name_": "اسم العرض", + "new_customer": "زبون جديد", + "customer_type": "نوع الزبون", + "customer_account": "حساب الزبون", + "business": "اعمال", + "individual": "فرد", + "display_name": "اسم العرض", + "the_customer_has_been_created_successfully": "تم إنشاء زبون جديد بنجاح.", + "select_contact": "حدد جهة اتصال", + "contact": "جهة اتصال", + "contacts": "جهات الاتصال", + "close_sidebar": "إغلاق الشريط الجانبي.", + "open_sidebar": "فتح الشريط الجانبي.", + "recalc_report": "إعادة حساب التقرير", + "remove_the_line": "حذف الصف", + "no_results": "لا نتائج.", + "all_reports": "كافة التقارير", + "next": "التالي", + "previous": "سابق", + "showing_current_page_to_total": "إظهار {currentPage} إلى {totalPages} من {total} من الإدخالات", + "new_child_account": "حساب فرعي جديد", + "contact_name": "اسم جهة الاتصال", + "company_name": "اسم الشركة", + "other": "آخر", + "address": "عنوان", + "attachement": "التعلق", + "country": "دولة", + "city_town": "المدينة / البلدة", + "state": "ولاية", + "zip_code": "الرمز البريدي", + "streat": "الشارع", + "edit_customer": "تعديل الزبون", + "delete_customer": "حذف الزبون", + "billing_address": "عنوان وصول الفواتير", + "shipping_address": "عنوان الشحن", + "customers_list": "قائمة الزبائن", + "receivable_balance": "رصيد المدين", + "the_customer_has_been_deleted_successfully": "تم حذف الزبون بنجاح.", + "the_customers_has_been_deleted_successfully": "تم حذف الزبائن بنجاح.", + "the_item_customer_has_been_edited_successfully": "تم تعديل الزبون بنجاح.", + "once_delete_this_customer_you_will_able_to_restore_it": "بمجرد حذف هذا الزبون ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف هذا الزبون؟", + "once_delete_these_customers_you_will_not_able_restore_them": "بمجرد حذف هؤلاء الزبائن ، لن تتمكن من استعادتهم لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "financial_accounting": " محاسبة مالية", + "after": "بعد", + "before": "قبل", + "count_filters_applied": "تم تطبيق {عدد} من الفلاتر", + "is": "هو", + "is_not": "ليس", + "create_a_new_view": "قم بإنشاء طريقة عرض جديدة", + "in": "في", + "not_equals": "لا يساوي", + "select_journal_type": "اختار نوع القيد", + "journal_type": "نوع القيد", + "journal_reference_hint": "مرجع فريد لهذه القيد. وهي محددة بـ 10 أحرف ويمكن أن تتكون من أحرف وأرقام وشرطة سفلية.", + "contact_column_hint": "عمود جهة الاتصال لتسجيل المبالغ المستحقة القبض والمستحقة الدفع لزبون او مورد.", + "make_journal_entry": "اضافة قيد يدوي", + "journal_number_is_already_used": "رقم القيد مستخدم بالفعل.", + "account_code_hint": "رقم فريد للحساب (بحد أقصى 10 أحرف)", + "logic_expression": "تعبير منطقي", + "assign_to_customer": "تعيين لزبون", + "inactive": "غير نشط", + "should_select_customers_with_entries_have_receivable_account": "يجب اختيار العملاء الذين لديهم إدخالات لديهم حسابات مستحقة القبض.", + "should_select_vendors_with_entries_have_payable_account": "يجب اختيار الموردين الذين لديهم إدخالات لديهم حسابات دائنة", + "vendors_should_selected_with_payable_account_only": "يجب تحديد جهات اتصال الموردين مع حساب \"الذمم الدائنة\" فقط.", + "customers_should_selected_with_receivable_account_only": "يجب تحديد جهات اتصال الزبائن مع حساب \"الذمم المدينة\" فقط.", + "amount_cannot_be_zero_or_empty": "لا يمكن أن تكون القيمة صفر أو فارغًة.", + "should_total_of_credit_and_debit_be_equal": "يجب أن يكون مجموع الدائن والمدين متساويين.", + "no_accounts": "لاتوجد حسابات", + "the_accounts_have_been_successfully_inactivated": "تم تعطيل الحسابات بنجاح.", + "account_code_is_not_unique": "رمز الحساب ليس فريدًا.", + "are_sure_to_publish_this_expense": "هل أنت متأكد أنك تريد نشر هذا المصروف؟", + "once_delete_these_journals_you_will_not_able_restore_them": "بمجرد حذف هذه المجلات ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "once_delete_this_journal_you_will_able_to_restore_it": "بمجرد حذف هذه القيد ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف ؟", + "the_expense_is_already_published": "تم نشر المصاريف بالفعل.", + "accounts_without_zero_balance": "حسابات ذات رصيد صفر", + "accounts_with_transactions": "حسابات مع معاملات", + "include_accounts_once_has_transactions_on_given_date_period": "قم بتضمين الحسابات التي لها معاملات في فترة التاريخ المحددة فقط.", + "include_accounts_and_exclude_zero_balance": "قم بتضمين الحسابات واستبعاد تلك التي لديها رصيد صفري.", + "all_accounts_including_with_zero_balance": "جميع الحسابات ، بما في ذلك تلك الحسابات لديها رصيد صفر.", + "notifications": "إشعارات", + "you_could_not_delete_account_has_child_accounts": "لا يمكنك حذف حساب لديه حسابات فرعية.", + "journal_entry": "القيد", + "estimate": "رقم العرض", + "estimate_date": "تاريخ العرض", + "expiration_date": "تاريخ انتهاء الصلاحية", + "customer_note": "ملاحظة الزبون", + "select_customer_account": "حدد حساب الزبون", + "select_product": "حدد المنتج", + "clear": "إعادة", + "save_send": "حفظ وإرسال", + "estimates": "العروض", + "edit_estimate": "تعديل العرض", + "delete_estimate": "حذف العرض", + "new_estimate": "عرض جديد", + "customer_name_": "اسم الزبون", + "estimate_date_": "تاريخ العرض", + "expiration_date_": "تاريخ انتهاء الصلاحية", + "estimate_number_": "رقم العرض", + "discount": "خصم ٪", + "quantity": "كمية", + "rate": "سعر", + "estimates_list": "قائمة العروض", + "estimate_number": "رقم العرض", + "product_and_service": "المنتج/الخدمة", + "the_estimate_has_been_edited_successfully": "تم تعديل العرض {number}# بنجاح.", + "the_estimate_has_been_created_successfully": "تم إنشاء عرض {number}# بنجاح.", + "the_estimate_has_been_deleted_successfully": "تم حذف العرض بنجاح.", + "once_delete_this_estimate_you_will_able_to_restore_it": "بمجرد حذف هذا العرض ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف؟", + "cannot_be_zero_or_empty": "لا يمكن أن يكون صفرًا أو فارغًا.", + "invoices": "الفواتير", + "invoices_list": "قائمة الفواتير", + "invoice_date": "تاريخ الفاتورة", + "due_date": "تاريخ الاستحقاق", + "invoice_date_": "تاريخ الفاتورة", + "invoice_no": "فاتورة #", + "invoice_no__": "رقم الفاتورة", + "invoice_no_": "رقم الفاتورة", + "due_date_": "تاريخ الاستحقاق", + "invoice_message": "رسالة الفاتورة", + "reference_no": "رقم المرجع", + "invocie_number": "رقم الفاتورة", + "invoice_amount": "قيمة الفاتورة", + "amount_due": "المبلغ المستحق", + "payment_amount": "مبلغ الدفع", + "edit_invoice": "تعديل الفاتورة", + "delete_invoice": "حذف الفاتورة", + "new_invoice": "فاتورة جديدة", + "invoice_list": "قائمة الفاتورة", + "the_invoice_has_been_edited_successfully": "تم تعديل الفاتورة رقم {number} بنجاح.", + "the_invoice_has_been_created_successfully": "تم إنشاء الفاتورة رقم {number} بنجاح.", + "the_invoice_has_been_deleted_successfully": "تم حذف الفاتورة بنجاح.", + "once_delete_this_invoice_you_will_able_to_restore_it": "بمجرد حذف هذه الفاتورة ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذه الفاتورة؟", + "receipts_list": "قائمة الإيصالات", + "receipts": "الإيصالات", + "receipt": "إيصال #", + "receipt_date_": "تاريخ استلام", + "receipt_date": "تاريخ استلام", + "deposit_account_": "حساب إيداع", + "receipt_message_": "رسالة الاستلام", + "receipt_no_": "رقم إيصال", + "edit_receipt": "تعديل الإيصال", + "delete_receipt": "حذف الإيصال", + "new_receipt": "إيصال جديد", + "receipt_message": "رسالة الإيصال", + "statement": "بيان", + "deposit_account": "حساب إيداع", + "send_to_email": "إرسال إلى بريد إلكتروني", + "select_deposit_account": "حدد حساب الإيداع", + "once_delete_this_receipt_you_will_able_to_restore_it": "بمجرد حذف هذا الإيصال ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف هذا الإيصال؟", + "the_receipt_has_been_created_successfully": "تم إنشاء الإيصال # {number} بنجاح.", + "the_receipt_has_been_edited_successfully": "تم تعديل الإيصال # {number} بنجاح.", + "the_receipt_has_been_deleted_successfully": "تم حذف الإيصال بنجاح.", + "bills_list": "قائمة فواتير الشراء", + "bills": "فواتير الشراء", + "accept": "قبول", + "vendor_name": "اسم المورد", + "select_vendor_account": "حدد حساب المورد", + "bill_date": "تاريخ الفاتورة", + "bill_number": "رقم الفاتوره", + "edit_bill": "تعديل الفاتورة", + "new_bill": "فاتورة شراء جديدة", + "bill_date_": "تاريخ الفاتورة", + "bill_number_": "رقم الفاتوره", + "vendor_name_": "اسم المورد", + "delete_bill": "حذف الفاتورة", + "the_bill_has_been_edited_successfully": "تم تعديل الفاتورة رقم {number} بنجاح.", + "the_bill_has_been_created_successfully": "تم إنشاء الفاتورة رقم {number} بنجاح.", + "the_bill_has_been_deleted_successfully": "تم حذف الفاتورة بنجاح.", + "once_delete_this_bill_you_will_able_to_restore_it": "بمجرد حذف هذه الفاتورة ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذه الفاتورة؟", + "deposit_to": "الإيداع", + "edit_payment_receive": "تعديل سند الزبون", + "delete_payment_receive": "حذف سند الزبون", + "payment_receives_list": "سندات الزبائن", + "payment_receive": "سند الزبون", + "new_payment_receive": "سند زبون جديد", + "payment_receives": "سندات الزبائن", + "payment_receive_no": "رقم سند الدفع", + "payment_receive_no_": "رقم سند الدفع", + "receive_amount": "استلام المبلغ", + "receive_amount_": "استلام المبلغ", + "the_payment_receive_transaction_has_been_created": "تم إنشاء سند الزبون بنجاح.", + "the_payment_receive_has_been_deleted_successfully": "تم حذف سند الزبون بنجاح.", + "the_payment_receive_transaction_has_been_edited": "تم تعديل معاملة سند الزبون بنجاح.", + "once_delete_this_payment_receive_you_will_able_to_restore_it": "بمجرد حذف هذا السند ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذه المعاملة؟", + "select_invoice": "حدد الفاتورة", + "payment_mades": "سندات الموردين", + "subscription": "الاشتراك", + "plan_slug": "سبيكة خطة", + "billing": "الفواتير", + "the_billing_has_been_created_successfully": "تم إنشاء الفواتير بنجاح.", + "select_a_plan": "حدد الباقة", + "choose_your_billing": "اختر مدة الدفع", + "payment_methods": "طريقة الدفع", + "usage": "إستعمال", + "basic": "أساسي", + "license": "رخصة", + "credit_card": "بطاقة إئتمان", + "paypal": "باي بال", + "pro": "احترافي", + "monthly": "شهريا", + "yearly": "سنوي", + "license_code": "رمز الترخيص", + "year": "سنة", + "please_enter_your_preferred_payment_method": "الرجاء إدخال طريقة الدفع المفضلة لديك أدناه. يمكنك استخدام بطاقة الائتمان / الخصم أو الدفع المسبق من خلال PayPal. ", + "cards_will_be_charged": "سيتم فرض رسوم على البطاقات إما في نهاية الشهر أو عندما يتجاوز رصيدك حد الاستخدام. جميع بطاقات الائتمان / الخصم الرئيسية مقبولة.", + "license_number": "رقم الرخصة", + "subscribe": "الإشتراك", + "year_per": "عام", + "payment_made": "سند الموردين", + "edit_payment_made": "تعديل سند الموردين", + "delete_payment_made": "حذف سند الموردين", + "payment_number": "رقم سند الدفع", + "payment_no": "رقم سند الدفع", + "bill_amount": "مبلغ الفاتورة", + "payment_no_": "رقم سند الدفع", + "new_payment_made": "سند موردين جديد", + "payment_made_list": "قائمة سندات الموردين", + "select_vender_account": "اختار حساب المورد", + "the_payment_made_has_been_edited_successfully": "تم تعديل سند المورد التي تم إجراؤه بنجاح.", + "the_payment_made_has_been_created_successfully": "تم إنشاء سند مورد بنجاح.", + "the_payment_made_has_been_deleted_successfully": "تم حذف سند المورد التي تم إجراؤه بنجاح.", + "once_delete_this_payment_made_you_will_able_to_restore_it": "بمجرد حذف هذا السند التي تم إجراؤها ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف هذه المعاملة؟", + "sellable": "قابل للبيع", + "purchasable": "قابل للشراء", + "sell_account": "بيع حساب", + "cost_account": "حساب التكلفة", + "register_a_new_organization_now": "سجل منشأة جديدة الآن !.", + "contact_us_technical_support": "اتصل بنا - الدعم الفني", + "let_s_get_started": "هيا بنا نبدأ", + "tell_the_system_a_little_bit_about_your_organization": "أخبر النظام قليلاً عن المنشأة.", + "organization_details": "تفاصيل المنشأة", + "financial_starting_date": "تاريخ البدء المالي ", + "note_you_can_change_your_preferences_later_in_dashboard_if_needed": "ملاحظة: يمكنك تغيير تفضيلاتك لاحقًا في لوحة التحكم ، إذا لزم الأمر.", + "save_continue": "حفظ ومتابعة", + "organization_register": "تسجيل منشأة", + "getting_started": "ابدء", + "payment_or_trial": "الدفع", + "initializing": "تجهيز حسابك", + "fiscal_year_": "السنة المالية", + "welcome": "مرحبا ", + "sign_out": "خروج", + "we_re_here_to_help": "نحن هنا للمساعدة!", + "date_start_": "تاريخ البدء", + "something_wentwrong": "هناك خطأ ما.", + "license_code_": "رمز الترخيص", + "legal_organization_name": "اسم منشأة القانوني", + "smaller_than": "اصغر من", + "smaller_or_equals": "أصغر أو يساوي", + "bigger_than": "أكبر من", + "bigger_or_equals": "أكبر أو يساوي", + "prefix": "اختصار", + "next_number": "الرقم التالي", + "journal_number_settings": "إعدادات رقم القيد", + "bill_number_settings": "إعدادات رقم الفاتورة", + "payment_number_settings": "إعدادات رقم الدفع", + "Estimate_number_settings": "إعدادات رقم العرض", + "receipt_number_settings": "إعدادات رقم الإيصال", + "invoice_number_settings": "إعدادات رقم الفاتورة", + "receipt_number": "عدد إيصال", + "estimate_number_is_not_unqiue": "الرقم العرض مستخدم مسبقاً.", + "invoice_number_is_not_unqiue": "رقم الفاتورة مستخدم مسبقأً.", + "sale_receipt_number_not_unique": "رقم إيصال مستخدم مسبقاً", + "sale_invoice_number_is_exists": "رقم فاتورة البيع موجود", + "bill_number_exists": "رقم الفاتورة موجود", + "ok": "نعم!", + "quantity_cannot_be_zero_or_empty": "لا يمكن أن تكون الكمية صفراً أو فارغة.", + "customer_email": "البريد إلكتروني الزبون", + "customer_phone_number": "رقم هاتف الزبون", + "opening_balance_at": "الرصيد الافتتاحي عند", + "opening_balance": "الرصيد الافتتاحي", + "balance_currency": "عملة الرصيد", + "financial_details": "التفاصيل المالية", + "are_you_sure_you_want_to_clear_this_transaction": "هل أنت متأكد أنك تريد مسح هذه المعاملة؟", + "clearing_the_table_lines_will_delete_all_credits": "سيؤدي مسح سطور الجدول إلى حذف جميع قيم الدائنة والدفع ، هل انت موافق؟", + "changing_full_amount_will_change_all_credits_and_payment": "سيؤدي تغيير المبلغ بالكامل إلى تغيير جميع الاعتمادات وتم تطبيق الدفع ، هل هذا مقبول؟", + "address_line_1": "العنوان 1", + "address_line_2": "العنوان 2", + "website": "موقع إلكتروني", + "notes": "ملاحظات", + "i_purchase_this_item": "نشتري في هذا العنصر من بائع.", + "i_sell_this_item": "ابيع هذا العنصر للزبائن", + "select_display_name_as": "حدد اسم العرض", + "opening_date": "تاريخ الافتتاح", + "item_code": "رمز المنتج", + "quantity_on_hand": "كمية في اليد", + "average_rate": "المعدل المتوسط", + "the_name_used_before": "الاسم مستخدم بالفعل.", + "the_item_has_associated_transactions": "المنتج له معاملات مرتبطة.", + "customer_has_sales_invoices": "لا يمكن حذف الزبون لديه فواتير المبيعات مرتبطة.", + "account_name_is_already_used": "اسم الحساب مستخدم بالفعل.", + "vendors": "الموردين", + "vendor_email": "البريد إلكتروني المورد", + "new_vendor": "مورد جديد", + "edit_vendor": "تعديل المورد", + "delete_vendor": "حذف المورد", + "vendors_list": "قائمة الموردين", + "the_vendor_has_been_created_successfully": "تم اضافة مورد جديد بنجاح.", + "the_vendor_has_been_deleted_successfully": "تم حذف المورد بنجاح.", + "the_item_vendor_has_been_edited_successfully": "تم تعديل المورد بنجاح.", + "once_delete_this_vendor_you_will_able_to_restore_it": "بمجرد حذف هذا المورد ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف؟", + "once_delete_these_vendors_you_will_not_able_restore_them": "بمجرد حذف هؤلاء الموردين ، لن تتمكن من استعادتهم لاحقًا. هل أنت متأكد أنك تريد حذفها؟", + "vendor_has_bills": "المورد لديه فواتير شراء.", + "you_cannot_make_payment_with_zero_total_amount": "لا يمكنك تسجيل معاملة الدفع بمبلغ إجمالي صفري", + "are_sure_to_publish_this_manual_journal": "هل أنت متأكد أنك تريد نشر هذا القيد اليدوي؟", + "save_publish": "احفظ وانشر", + "publish_and_new": "نشر وجديد", + "publish_continue_editing": "نشر (متابعة التعديل)", + "save_and_new": "حفظ وجديد", + "save_continue_editing": "حفظ (متابعة التعديل)", + "reset": "إعادة ضبط ", + "save_and_send": "احفظ وأرسل", + "posting_date": "تاريخ النشر", + "customer": "زبون", + "email_is_already_used": "البريد الإلكتروني مستخدم بالفعل.", + "the_item_categories_has_been_deleted_successfully": "تم حذف تصنيفات المنتجات بنجاح.", + "receivable_accounts_should_assign_with_customers": "حسابات القبض مع العملاء.", + "delivered": "تم التوصيل", + "save_and_deliver": "حفظ وتسليم", + "deliver_and_new": "تسليم وجديد", + "deliver_continue_editing": "تسليم (متابعة التعديل)", + "due_in": "موعد التسليم {due} يوم.", + "day_partially_paid": "مدفوعة جزئياً ، {due} مستحق.", + "overdue_by": "تأخرت بحلول {overdue} يوم.", + "paid": "مدفوع", + "your_account_has_been_locked": "تم قفل حسابك بسبب محاولات تسجيل الدخول الفاشلة المتكررة. يرجى الانتظار بضع دقائق قبل المحاولة مرة أخرى.", + "the_invoice_has_been_delivered_successfully": "تم تسليم الفاتورة بنجاح.", + "are_sure_to_deliver_this_invoice": "هل أنت متأكد أنك تريد تسليم هذه الفاتورة؟", + "mark_as_delivered": "وضع علامة \"تم التسليم\"", + "deliver": "ايصال", + "mark_as_closed": "وضع علامة مغلق", + "mark_as_opened": "وضع علامة مفتوح", + "save_close": "حفظ وإغلاق", + "save_open": "حفظ وفتح", + "close_and_new": "قريب وجديد", + "close_continue_editing": "إغلاق (متابعة التعديل)", + "the_receipt_has_been_closed_successfully": "تم إغلاق الإيصال بنجاح.", + "are_sure_to_close_this_receipt": "هل أنت متأكد أنك تريد إغلاق هذا الإيصال؟", + "closed": "مغلق", + "open_and_new": "مفتوح وجديد", + "open_continue_editing": "فتح (متابعة التعديل)", + "the_bill_has_been_opened_successfully": "تم فتح الفاتورة بنجاح.", + "open": "فتح", + "are_sure_to_open_this_bill": "هل أنت متأكد أنك تريد فتح هذه الفاتورة؟", + "opened": "مفتوح", + "the_estimate_has_been_delivered_successfully": "تم تسليم العرض بنجاح.", + "the_estimate_has_been_approved_successfully": "تمت الموافقة على العرض بنجاح.", + "the_estimate_has_been_rejected_successfully": "تم رفض العرض بنجاح.", + "are_sure_to_deliver_this_estimate": "هل أنت متأكد أنك تريد تسليم هذا العرض؟", + "approve": "يوافق", + "are_sure_to_approve_this_estimate": "هل أنت متأكد أنك تريد الموافقة على هذا العرض؟", + "reject": "رفض", + "are_sure_to_reject_this_estimate": "هل أنت متأكد أنك تريد رفض هذا العرض؟", + "mark_as_approved": "وضع علامة على أنه موافق عليه", + "mark_as_rejected": "وضع علامة \"مرفوض\"", + "rejected": "مرفوض", + "approved": "وافق", + "the_item_has_been_inactivated_successfully": "تم إلغاء تنشيط المنتج بنجاح.", + "the_item_has_been_activated_successfully": "تم تفعيل المنتج بنجاح.", + "are_sure_to_inactive_this_item": "هل أنت متأكد أنك تريد إلغاء تنشيط هذا المنتج؟ ستكون قادرًا على تنشيطه لاحقًا", + "are_sure_to_activate_this_item": "هل أنت متأكد أنك تريد تفعيل هذا المنتج؟ ستتمكن من تعطيله لاحقًا", + "inactivate_item": "تعطيل المنتج", + "activate_item": "تنشيط المنتج", + "all_payments": "جميع المدفوعات", + "hide_customizer": "إخفاء أداة التخصيص", + "opening_quantity_": "كمية الافتتاح", + "opening_average_cost": "متوسط تكلفة الافتتاح", + "opening_cost_": "تكلفة الافتتاح ", + "opening_date_": "تاريخ الافتتاح", + "the_invoice_cannot_be_deleted": "لا يمكن حذف الفاتورة لأن هناك حركات دفع مرتبطة بها", + "category_name_exists": "اسم التصنيف موجود.", + "some_customers_have_sales_invoices": "بعض الزبائن لديهم فواتير مبيعات", + "inventory_adjustments": "تسويات المخزون", + "make_adjustment": "تسوية المخزون", + "adjustment_type": "نوع التسوية", + "decrement": "إنقاص", + "new_quantity": "كمية جديدة", + "reason": "السبب", + "increment": "زيادة", + "cost": "التكلفة", + "qty_on_hand": "الكمية الحالية", + "adjustment_account": "حساب التسوية", + "inventory_adjustment_list": "تسويات المخزون", + "delete_adjustment": "حذف التسوية", + "the_make_adjustment_has_been_created_successfully": "تم إنشاء تسوية علي المنتج بنجاح.", + "the_adjustment_has_been_deleted_successfully": "تم حذف التسوية بنجاح.", + "once_delete_this_inventory_a_adjustment_you_will_able_to_restore_it": "بمجرد حذف هذا المخزون تعديلًا ، لن تتمكن من استعادته لاحقًا. هل أنت متأكد أنك تريد حذف هذه الفاتورة؟", + "select_adjustment_account": "حدد حساب التسوية", + "qty": "الكمية الحالية", + "money_format": "شكل القيم المالية", + "show_zero": "عرض الصفر.", + "show_negative_in_red": "إظهار القيم السالبة باللون الأحمر.", + "divide_on_1000": "اقسم على 1000.", + "negative_format": "تنسيق قيم السالبة", + "decimal_places": "منازل عشرية", + "run": "يركض", + "you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions": "لا يمكن حذف المنتج لديه معاملات تسوية علي المخزون.", + "format": "صيغة", + "current": "الحالي", + "adjustment_reasons": "أسباب التسوية", + "specific_customers": "زبائن محددين", + "all_customers": "كل الزبائن", + "all_vendors": "كل الموردين", + "selected_customers": "{عدد} العملاء المحددين", + "transaction_number": " العملية", + "running_balance": "الرصيد التحليلي", + "view_all": "مشاهدة الكل", + "payment_via_voucher": "الدفع عن طريق القسيمة", + "voucher_number": "رقم القسيمة", + "voucher": "إيصال", + "payment_number_is_not_unique": "رقم معاملة الدفع مستخدم من قبل.", + "change_full_amount": "تغيير المبلغ بالكامل", + "view_paper": "مشاهدة ورقة", + "estimate_paper": "فاتورة العرض", + "invoice_paper": "رقم الفاتورة", + "receipt_paper": "فاتورة الواصل", + "payable_aging_summary": "ملخص اعمار الديون الدائنة", + "payment_receive_paper": "ورقة استلام الدفع", + "specific_vendors": "موردين محددون", + "accounts_receivable_a_r": "الذمم المدينة", + "accounts_payable_a_p": "الذمم الدائنة", + "products_services_inventory": "المنتجات والخدمات والمخزون", + "payable_a_p": "مستحق الدفع أ / ف", + "keyboard_shortcuts": "اختصارات لوحة المفاتيح", + "shortcut_keys": "مفاتيح الاختصار", + "oK_": "نعم", + "convert_to_invoice": "تحويل إلى فاتورة", + "sale_estimate_is_already_converted_to_invoice": "تم بالفعل تحويل العرض البيع إلى فاتورة.", + "duplicate": "تكرار", + "are_you_sure_want_to_duplicate": "هل أنت متأكد من أنك تريد تكرار جهة الاتصال هذه ، ما نوع جهة الاتصال؟", + "contact_type": "نوع جهة الاتصال", + "duplicate_contact": "تكرار جهة الاتصال", + "contact_type_": "نوع جهة اتصال", + "the_payment_amount_that_received": "مبلغ الدفع الذي تم استلامه من الزبون أكبر من المبلغ المستحق لهذه الفاتورة.", + "invoice_number": "رقم الفاتورة", + "make_payment": "قم بالدفع", + "add_payment": "إضافة الدفع", + "quick_receive_payment": "سند قبض سريع", + "amount_received": "تم استلام المبلغ", + "payment_receive_number_required": "مطلوب رقم استلام الدفعة", + "quick_made_payment": "سند دفع سريع", + "the_payment_amount_bigger_than_invoice_due_amount": "مبلغ الدفعة أكبر من مبلغ الفاتورة المستحق.", + "accounting_basis_": "الأساس المحاسبي", + "deposit_customer_account": "إيداع حساب الزبون", + "withdrawal_vendor_account": "سحب حساب المورد", + "customer_advance_deposit": "إيداعات الزبون المقدمة", + "cannot_delete_bill_that_has_payment_transactions": "لا يمكن حذف الفاتورة التي لها معاملات دفع مرتبطة.", + "cannot_change_item_type_to_inventory_with_item_has_associated_transactions": "لا يمكن تغيير نوع المنتج إلى المخزون لانة لديه معاملات مرتبطة بالعنصر.", + "work_phone": "هاتف عمل", + "cannot_delete_vendor_that_has_associated_purchase_bills": "لا يمكن حذف المورد الذي لديه فواتير شراء.", + "the_accountant_preferences_has_been_saved": "تم حفظ تفضيلات المحاسب.", + "the_items_preferences_has_been_saved": "تم حفظ تفضيلات المنتجات.", + "preferred_sell_account": "حساب البيع المفضل", + "preferred_cost_account": "حساب التكلفة المفضل", + "preferred_inventory_account": "حساب المخزون المفضل", + "this_customer_cannot_be_deleted_as_it_is_associated_with_transactions": "لا يمكن حذف هذا الزبون لأنه لديه معاملات مرتبطة به.", + "this_vendor_cannot_be_deleted_as_it_is_associated_with_transactions": "لا يمكن حذف هذا المورد لأنه لديه معاملات مرتبطة به.", + "currency_sign": "علامة العملة", + "cannot_change_item_inventory_account": "لا يمكن تغيير حساب مخزون للمنتج لانة لديه معاملات مرتبطة به.", + "purchases_by_items": "المشتريات حسب المنتجات", + "quantity_purchased": "الكمية المشتراة", + "purchase_amount": "مبلغ الشراء", + "average_price": "متوسط السعر", + "sales_by_items": "المبيعات حسب المنتجات", + "sold_quantity": "الكمية المباعة", + "sold_amount": "المبلغ المباع", + "asset_value": "قيمة الأصول", + "average": "المتوسط", + "inventory_valuation": "تقييم المخزون", + "payable_accounts_should_assign_with_vendors": "يجب تعيين حسابات الدفع مع البائعين.", + "account_paper": "ورقة الحساب", + "transaction_date": "تاريخ العملة", + "account_normal": "طبيعة الحساب", + "published_at": "نشرت في", + "customers_balance_summary": "ملخص رصيد الزبائن", + "vendors_balance_summary": "ملخص رصيد الموردين ", + "percentage_of_column": "نسبة مئوية", + "customers_transactions": "معاملات الزبائن", + "vendors_transactions": "معاملات الموردين", + "reference_type": "نوع العملية", + "cash_flow_statement": "قائمة التدفقات النقدية", + "statement_of_cash_flow": "قائمة الدفقات النقدية", + "inventory_item_details": "تفاصيل منتج المخزون", + "sales_invoices": "فواتير البيع", + "tracking_sales_invoices_with_your_customers": "تتبع فواتير المبيعات مع عملائك بتاريخ استحقاق الدفع.", + "sales_estimates": "عروض البيع", + "manage_your_sales_estimates_to_create_quotes": "إدارة عروض المبيعات الخاصة بك لإنشاء عروض أسعار لكي يتم تحويلها إلى فاتورة بيع.", + "sales_receipts": "إيصالات", + "manage_sales_receipts_for_sales_that_get_paid": "إدارة إيصالات للمبيعات التي يتم دفعها فوراً من الزبون.", + "manage_the_customers_relations_with_customer": "إدارة علاقات الزبائن مع الذمم المدينة للزبائن والأرصدة الدائنة.", + "customers_payment": "Customers payment", + "manage_payment_transactions_from_your_customers": "إدارة معاملات الدفع من زبائنك بفواتير البيع.", + "purchase_invoices": "فواتير الشراء", + "manage_the_purchase_invoices_with_your_vendors": "إدارة فواتير الشراء مع الموردين الخاصين بك مع تاريخ استحقاق الدفع.", + "manage_the_vendors_relations_with_vendor_relations": "إدارة علاقات الموردين مع أرصدة الذمم الدائنة والمدينة.", + "vendors_payments": "مدفوعات الموردين", + "manage_payments_transactions_to_your_vendors": "إدارة معاملات المدفوعات للموردين مع فواتير الشراء.", + "manage_your_accounts_chart_to_record_your_transactions_and_categories": "قم بإدارة شجرة حساباتك لتسجيل معاملاتك وتصنيفها في الحسابات الأم.", + "manage_manual_journal_transactions_on_accounts": "إدارة معاملات القيود اليدوية على الحسابات ومركز الكلفة والمشاريع.", + "track_your_indirect_expenses_under_specific_categories": "تتبع نفقاتك غير المباشرة ضمن فئات محددة مثل الرواتب والإيجار.", + "financial_statements": "القوائم المالية", + "show_financial_reports_about_your_organization": "تعرض القوائم المالية حول المنشأة لتلخيص الأداء المالي لنشاطك التجاري.", + "products_services": "المنتجات والخدمات", + "manage_your_products_inventory_or_non_inventory": "إدارة المنتجات (مخزون أو ليست مخزون) والخدمات ووضعها الي مجموعة تصنيفات.", + "products_services_categories": "تصنيفات المنتجات والخدمات", + "group_your_products_and_service": "قم بتصنيف منتجاتك وخدماتك في تصنيفات مختلفة.", + "manage_your_inventory_adjustment_of_inventory_items": "إدارة تسويات المخزون الخاص بك للمنتجات المخزون التي تمتلكها المنشأة.", + "page_size": "حجم الصفحة", + "there_is_no_items_categories_in_table_yet": "لا توجد تصنيفات المنتجات في الجدول إلي حد الأن.", + "sales_inventory": "مبيعات ومخزون", + "accounting": "محاسبة", + "system": "النظام", + "it_s_time_to_send_estimates_to_your_customers": "حان الوقت لإرسال العروض إلى زبائنك", + "it_is_a_long_established_fact_that_a_reader": " It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.", + "new_sale_estimate": "عرض بيع جديد", + "learn_more": "أعرف أكثر", + "back_to_list": "العودة للقائمة.", + "estimate_details": "تفاصيل العرض", + "attachments_maximum": "المرفقات: الحجم الأقصى: 20MB", + "drag_drop_files_here_or_click_here": "قم بسحب/إسقاط الملفات هنا أو انقر هنا.", + "enter_an_item": "أدخل منتج ...", + "i_will_enter_them_manually_each_time": "سأدخلها يدويًا في كل مرة", + "manual_entering_for_this_transaction": "إدخال يدوي لهذه العملية فقط.", + "due_amount": "القيمة المستحقة", + "invoice_details": "تفاصيل الفاتورة", + "setting_your_auto_generated_estimate_number": "عيين رقم العرض الذي تم إنشاؤه تلقائيًا", + "setting_your_auto_generated_journal_number": "عيين رقم قيد اليدوي الذي تم إنشاؤه تلقائيًا", + "setting_your_auto_generated_invoice_number": "عيين رقم الفاتورة الذي تم إنشاؤه تلقائيًا", + "setting_your_auto_generated_payment_receive_number": "عيين رقم استلام الدفعة الذي تم إنشاؤه تلقائيًا", + "the_organization_doesn_t_receive_money_yet": "المنشأة لم تستلم اي اموال من الزبائن ، إلي حد الأن!.", + "there_is_no_receivable_invoices_for_this_customer": "لا توجد فواتير مستحقة لهذا الزبون يمكن عرضها لتسديدها.", + "please_select_a_customer_to_display_all_open_invoices_for_it": "يرجى تحديد الزبون لعرض جميع الفواتير المفتوحة له.", + "payment_receive_details": "تفاصيل سند الزبون", + "receive_full_amount": "استلام المبلغ كاملاً", + "manage_the_organization_s_services_and_products": "إدارة الخدمات والمنتجات للمنشأة.", + "here_a_list_of_your_organization_products_and_services": "هنا قائمة بمنتجات وخدمات عملك ، لاستخدامها عند إنشاء فواتير أو فواتير لموردين أو البائعين.", + "receipt_details": "تفاصيل الإيصال", + "bill_details": "تفاصيل الفاتورة", + "new_bill_payment": "سند مورد جديد", + "new_sale_invoice": "فاتورة بيع جديدة", + "there_is_no_payable_bills_for_this_vendor_that_can_be_applied_for_this_payment": "لا توجد فواتير مستحقة الدفع لهذا المورد يمكن ادخالها لهذه الدفعه.", + "please_select_a_vendor_to_display_all_open_bills_for_it": "الرجاء تحديد المورد لعرض كافة الفواتير الشراء المفتوحة له.", + "payment_made_details": "تفاصيل سند الزبون", + "there_is_no_inventory_adjustments_transactions_yet": "لا توجد معاملات تسوية للمخزون حتى الآن.", + "create_and_manage_your_organization_s_customers": "إنشاء وإدارة عملاء مؤسستك.", + "salutation": "تحية", + "work": "العمل", + "mobile": "الهاتف المحمول", + "phone": "الهاتف", + "create_and_manage_your_organization_s_vendors": "اضافة وإدارة موردين المنشأة.", + "balance_sheet_report": "الميزانية العمومية", + "reports_a_company_s_assets_liabilities_and_shareholders": "يعرض أصول الشركة والتزاماتها وحقوق المساهمين في نقطة زمنية محددة مع فترة (فترات) المقارنة.", + "summarizes_the_credit_and_debit_balance_of_each_account": "يلخص الرصيد الدائن والمدين لكل حساب في شجرة الحسابات الخاص بك في نقطة زمنية محددة.", + "profit_loss_report": "قائمة الدخل", + "reports_the_revenues_costs_and_expenses": "يعرض الإيرادات والتكاليف والمصروفات المتكبدة خلال نقطة زمنية محددة مع فترة (فترات) المقارنة.", + "reports_inflow_and_outflow_of_cash_and_cash_equivalents": "يعرض التدفقات النقدية الداخلة والخارجة وما يعادله بين نقطتين زمنيتين محددتين.", + "journal_report": "دفتر اليومية", + "the_debit_and_credit_entries_of_system_transactions": " القيود الدائنة والمدينة لمعاملات النظام ، مرتبة حسب التاريخ.", + "general_ledger_report": "دفتر الأستاذ العام", + "reports_every_transaction_going_in_and_out_of_your": "يعرض معاملات الحسابات الداخلة والخارجة مجمعة بحسب كل حساب ومرتبة بالتاريخ لمراقبة نشاط كل حساب.", + "summarize_total_unpaid_balances_of_customers_invoices": "يلخص إجمالي الأرصدة غير المدفوعة لفواتير الزبائن مع عدد أيام تأخر الفاتورة غير المدفوعة.", + "summarize_total_unpaid_balances_of_vendors_purchase": "يلخص إجمالي الأرصدة غير المدفوعة لفواتير شراء الموردين بعدد الأيام التي تأخرت فيها الفاتورة غير المدفوعة.", + "sales_purchases_reports": "تقارير المشتريات والمبيعات", + "reports_every_transaction_going_in_and_out_of_your_items": "يعرض كل معاملة تدخل وتخرج عن المنتجات الخاصة بك لمراقبة عمليات كل منتج.", + "reports_every_transaction_going_in_and_out_of_each_vendor_supplier": "يعرض كل معاملة داخلة وخارجة تمت عن كل مورد.", + "reports_every_transaction_going_in_and_out_of_each_customer": "يعرض كل معاملة داخلة وخارجة تمت عن كل زبون.", + "summerize_the_total_amount_your_business_owes_each_vendor": "يلخص المبلغ الإجمالي الذي يدين به عملك لكل مورد.", + "summerize_the_total_amount_of_each_customer_owes_your_business": "يلخص المبلغ الإجمالي لكل زبون مدين لعملك.", + "summarize_the_business_s_purchase_items_quantity_cost_and_average": "يلخص كمية المنتجات المشتراه والتكلفة ومتوسط معدل التكلفة لكل منتج في المخزون خلال فترة زمنية محددة.", + "summarize_the_business_s_sold_items_quantity_income_and_average_income_rate": "يلخص كمية المنتجات المباعة والدخل ومتوسط معدل الدخل لكل منتج في المخزون خلال فترة زمنية محددة.", + "shows_the_average_age_of_unresolved_issues_for_a_project_or_filter": "Shows the average age of unresolved issues for a project or filter. This helps you see whether your backlog is being kept up to date", + "categories": "التصنيفات", + "duplicate_item": "تكرار المنتج", + "summerize_your_transactions_for_each_inventory_item": "يلخص معاملات كل منتج في المخزون وكيف تؤثر على الكمية والتقييم والمتوسط المرجح.", + "summerize_how_much_each_customer_owes_your_business": "يلخص المبلغ الإجمالي الذي يدين به كل زبون لعملك.", + "duplicate_customer": "تكرار الزبون", + "duplicate_vendor": "تكرار المورد", + "new_billing": "فاتورة جديدة", + "shown_on_sales_forms_and_purchase_orders": "", + "for_reporting_you_can_specify_any_month": "لإعداد التقارير ، يمكنك تحديد أي شهر على أنه بداية السنة المالية الخاصة بك (تسمى أيضًا سنة إعداد التقارير المالية أو سنة المحاسبة).", + "you_can_t_change_the_base_currency_as_there_are_transactions": "لا يمكنك تغيير العملة الأساسية بينما توجد معاملات مسجلة في حساب المنشأة.", + "make_account_code_required_when_create_a_new_accounts": "اجعل رمز الحساب مطلوبًا عند إنشاء حسابات جديدة.", + "should_account_code_be_unique_when_create_a_new_account": "يجب أن يكون رمز الحساب فريدًا عند إنشاء حساب جديد.", + "select_a_preferred_account_to_deposit_into_it_after_customer_make_payment": "حدد الحساب المفضل للإيداع فيه بعد قيام الزبون بالدفع.", + "select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits": "حدد حساب المفضل لإيداع فيه ودائع الموردين المقدمة.", + "roles": "الأدوار", + "closing_balance": "الرصيد الختامي", + "view_more_transactions": "عرض المزيد من المعاملات.", + "there_is_no_results_in_the_table": "لا توجد نتائج في الجدول.", + "create_your_first_journal_entries_on_accounts_chart": "قم بإدخال اول القيود اليدوية علي شجرة الحسابات.", + "journal_details": "تفاصيل القيد", + "create_and_manage_your_organization_s_expenses": "إضافة وإدارة مصاريف مؤسستك", + "expense_amount": "قيمة المصروف", + "expense_details": "تفاصيل المصروف", + "sub_total": "مبلغ إجمالي", + "estimate_": "العرض", + "billed_to": "تم دفع لـ ", + "estimate_no": "رقم العرض", + "billed_from": "تم دفع من", + "estimate_amount": "قيمة العرض", + "invoice": "الفاتورة", + "receipt_": "الإيصال", + "receipt_no": "رقم الإيصال", + "receipt_amount": "قيمة الإيصال", + "please_enter_your_preferred_payment_method_below": "الرجاء إدخال طريقة الدفع المفضلة لديك أدناه.", + "submit_voucher": "ادخال القسيمة", + "sale_invoice": "فاتورة بيع", + "purchase_invoice": "فاتورة شراء", + "expense": "مصروف", + "vendor": "مورد", + "jump_to_the_invoices": "انتقل إلى فوتير البيع.", + "jump_to_the_estimates": "انتقل إلى العروض.", + "jump_to_the_receipts": "انتقل إلي الإيصالات.", + "jump_to_the_expenses": "انتقل إلي مصاريف.", + "jump_to_the_customers": "انتقل إلي الزبائن", + "jump_to_the_vendors": "انتقل إلي الموردين.", + "jump_to_the_chart_of_accounts": "انتقل إلي شجرة الحسابات.", + "jump_to_the_bills": "انتقل إلي فواتير الشراء.", + "jump_to_the_manual_journals": "انتقل إلي القيود اليدوية.", + "jump_to_the_items": "انتقل إلي المنتجات.", + "jump_to_the_balance_sheet": "انتقل إلي تقرير الميزانية العمومية.", + "jump_to_the_profit_loss_sheet": "انتقل إلي تقرير قائمة الدخل.", + "jump_to_the_journal_sheet": "انتقل إلي دفتر اليومية العامة.", + "jump_to_the_general_ledger_sheet": "انتقل إلي دفتر الأستاذ العام", + "jump_to_the_trial_balance_sheet": "انتقل إلي تقرير ميزان المراجعة", + "create_a_new_invoice": "انتقل إلي اضافة فاتورة بيع جديدة.", + "create_a_new_estimate": "انتقل إلي إضافة عرض جديد.", + "create_a_new_receipt": "إضافة إيصال جديد.", + "create_a_new_expense": "إضافة مصروف جديد.", + "create_a_new_customer": "إضافة زبون جديد.", + "create_a_new_vendor": "إضافة مورد جديد.", + "create_a_new_bill": "إضافة فاتورة شراء جديدة", + "create_a_new_item": "إضافة منتج جديد.", + "close_and_open_sidebar": "إغلاق وفتح الشريط الجانبي.", + "and_over": "وأكثر", + "date_year": "التاريخ / السنة", + "date_month": "التاريخ / الشهر", + "date_week": "التاريخ / الأسبوع", + "date_day": "التاريخ / اليوم", + "date_quarter": "التاريخ / الربع", + "today": "اليوم", + "this_week": "هذا الأسبوع", + "this_month": "هذا الشهر", + "this_quarter": "هذا الربع", + "this_year": "هذه السنة", + "custom_range": "نطاق مخصص", + "total_rows": "", + "always": "دائماً", + "none": "", + "us_dollar": "", + "euro": "", + "libyan_diner": "", + "english": "", + "arabic": "", + "just_a_moment_we_re_calculating_your_cost_transactions": "لحظة واحدة! نحن نحسب معاملات التكلفة الخاصة بك ، هذا لا يستغرق الكثير من الوقت ، يرجى التحقق بعد فترة.", + "refresh": "", + "total_name": "إجمالي {name}", + "income": "الدخل", + "total_income": "إجمالي الدخل", + "cost_of_sales": "تكلفة المبيعات", + "total_cost_of_sales": "التكلفة الإجمالية للمبيعات", + "gross_profit": "إجمالي الأرباح", + "total_expenses": "إجمالي المصروفات", + "net_operating_income": "صافي الدخل التشغيلي", + "other_income": "الإيرادات الأخرى", + "total_other_income": "إجمالي الدخل اخرى", + "other_expenses": "مصاريف اخري", + "total_other_expenses": "إجمالي المصاريف اخرى", + "net_other_income": "صافي الدخل الآخر", + "net_income": "صافي الدخل", + "services_that_you_provide_to_customers": " الخدمة: الخدمات التي تقدمها للعملاء.", + "products_you_buy_and_or_sell": " المخزون: المنتجات التي تشتريها و / أو تبيعها والتي تتبع كمياتها.", + "products_you_buy_and_or_sell_but_don_t_need": " غير متعلق بالمخزون: المنتجات التي تشتريها و / أو تبيعها ولكنك لا تحتاج (أو لا تستطيع) تتبع كميات ، على سبيل المثال المسامير المستخدمة في التثبيت.", + "there_is_no_items_in_the_table_yet": "لا توجد منتجات في الجدول حتى الآن.", + "congrats_you_are_ready_to_go": "مبروك! انت جاهز للبدء", + "go_to_dashboard": "انتقل إلي الصفحة الرئيسية,", + "mr": "سيد", + "mrs": "سيدة", + "ms": "آنسة", + "miss": "آنسة", + "dr": "دكتور", + "all_accounts_": "كل الحسابات", + "search": "البحث ...", + "starter": "", + "sale_and_purchase_invoices": "فواتير الشراء والبيع", + "customers_vendors_accounts": "حسابات الزبائن والموردين", + "expense_tracking": "تتبع المصاريف", + "for_one_user_and_accountant": "لمستخدم واحد والمحاسب", + "all_capital_starter_features": "جميع مميزات Capital Starter", + "multi_currency": "تعدد العملات", + "purchase_and_sell_orders": "أوامر الشراء والبيع.", + "inventory_management": "ادارة المخزون.", + "three_users_with_your_accountant": "ثلاثة مستخدمين مع المحاسب الخاص بك.", + "advanced_financial_reports": "التقارير المالية المتقدمة.", + "all_capital_essential_features": "جميع مميزات Capital Essential", + "track_multi_branches_and_locations": "تتبع الفروع المتعددة والمواقع.", + "projects_accounting_and_timesheets": "محاسبة المشاريع والجداول الزمنية.", + "accounting_dimensions": "محاسبة الأبعاد", + "libya": "ليبيا", + "mm_dd_yy": "", + "dd_mm_yy": "", + "yy_mm_dd": "", + "mm_dd_yy_": "", + "dd_mm_yy_": "", + "yy_mm_dd_": "", + "plan_radio_name": "", + "create_a_new_journal": "اضافة قيد يدوي جديد", + "customers_payments": "مدفوعات الزبائن", + "receiving_customer_payments_is_one_pleasant_accounting_tasks": "استلام المدفوعات أحد مهام المحاسبية الأكثر متعة. ستظهر معاملات المدفوعات بمجرد استلام المدفوعات للفواتير.", + "estimate_is_used_to_create_bid_proposal_or_quote": "تستخدم العروض لإنشاء عطاء أو اقتراح أو عرض أسعار منتجات. يمكن تحويل العرض إلى أمر مبيعات أو فاتورة.", + "payment_made_empty_status_description": "ستظهر معاملات المدفوعات بمجرد سداد فواتير المشتريات.", + "bill_empty_status_description": "", + "invoices_empty_status_description": "فواتير المبيعات للعملاء الذين لا يسددون أي مدفوعات جزئية أو يسددونها ، مع مساعدتك في تتبع الحسابات المستحقة مع الزبائن.", + "receipt_empty_status_description": "إيصالات المبيعات للمبيعات التي يدفع فيها الزبون فورًا مقابل المنتجات أو الخدمات في وقت البيع.", + "there_were_no_purchases_during_the_selected_date_range": "لا توجد مشتريات خلال النطاق الزمني المحدد.", + "there_were_no_sales_during_the_selected_date_range": "لا توجد مبيعات خلال النطاق الزمني المحدد.", + "there_were_no_inventory_transactions_during_the_selected_date_range": "لم تكن هناك حركات مخزون خلال نطاق الزمني المحدد.", + "filter_": "البحث...", + "all_rights_reserved": "© {pre}-{current} كل الحقوق محفوظة.", + "congrats_your_account_has_been_created_and_invited": "مبروك! تم إنشاء حسابك ودعوتك إلى {organization_name} بنجاح.", + "it_s_time_to_make_your_accounting_really_simple": "حان الوقت لجعل عملية المحاسبة الخاصة بك بسيطة واكتر دقة!", + "while_we_set_up_your_account_please_remember_to_verify_your_account": "أثناء قيامنا بإعداد حسابك ، يرجى تذكر التحقق من حسابك بالنقر فوق الارتباط الذي أرسلناه إلى عنوان بريدك الإلكتروني المسجل", + "something_went_wrong": "هناك خطأ ما!", + "please_refresh_the_page": "يرجى تحديث الصفحة", + "waiting_to_redirect": "في انتظار إعادة التوجيه", + "refresh_the_page_if_redirect_not_worked": "قم بتحديث الصفحة إذا لم تنجح إعادة التوجيه." +} \ No newline at end of file diff --git a/client/src/lang/ar-ly/locale.js b/client/src/lang/ar-ly/locale.js new file mode 100644 index 000000000..bd7d5db24 --- /dev/null +++ b/client/src/lang/ar-ly/locale.js @@ -0,0 +1,64 @@ +// Based on https://github.com/jquense/yup/blob/2973d0a/src/locale.js + +import printValue from '../printValue'; + +export const locale = { + mixed: { + default: '${path} غير صالح.', + required: '${path} هو حقل مطلوب', + oneOf: '${path} يجب أن تكون واحدة من القيم التالية: ${values}', + notOneOf: '${path} لا يجب أن تكون واحدة من القيم التالية: ${values}', + notType: ({ path, type, value, originalValue }) => { + const isCast = originalValue != null && originalValue !== value; + let msg = + `${path} يجب أن يكون \`${type}\` نوع, ` + + `ولكن القيمة النهائية كانت في: \`${printValue(value, true)}\`` + + (isCast + ? ` (المدلى بها من قيمة \`${printValue(originalValue, true)}\`).` + : '.'); + + if (value === null) { + msg += + `\n إذا كان المقصود "لاغية" كقيمة فارغة مما لا شك فيه للاحتفال مخطط كما` + + ' `.nullable()`'; + } + + return msg; + }, + }, + + string: { + length: '${path} يجب أن يكون بالضبط ${length} حرفا', + min: '${path} يجب أن تكون على الأقل ${min} حرفا', + max: '${path} يجب أن تكون على الأكثر ${max} حرفا', + matches: '${path} يجب أن يطابق ما يلي: "${regex}"', + email: '${path} يجب أن يكون عنوان بريد إلكتروني صالح', + url: '${path} يجب أن يكون عنوان URL صالحا', + trim: '${path} يجب أن تكون سلسلة قلص', + lowercase: '${path} يجب أن تكون سلسلة صغيرة', + uppercase: '${path} يجب أن تكون سلسلة الحالة العلوي', + }, + + number: { + min: '${path} يجب أن تكون أكبر من أو يساوي ${min}', + max: '${path} يجب أن يكون أقل من أو يساوي ${max}', + lessThan: '${path} يجب أن يكون أقل من ${less}', + moreThan: '${path} يجب أن تكون أكبر من ${more}', + positive: '${path} يجب أن يكون رقما موجبا', + negative: '${path} يجب أن يكون رقما سالبا', + integer: '${path} يجب أن يكون رقما', + }, + date: { + min: '${path} يجب أن يكون حقل في وقت لاحق من ${min}', + max: '${path} يجب أن يكون حقل في وقت سابق من ${max}', + }, + boolean: {}, + + object: { + noUnknown: '${path} حقل لا يمكن أن يكون مفاتيح غير محددة في شكل وجوه', + }, + array: { + min: 'يجب أن يكون ${path} حقل على الأقل ${min} من العناصر', + max: '${path} يجب أن يكون الحقل أقل من أو يساوي إلى ${max} من العناصر', + }, +}; diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 987743345..9b072d26c 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -1064,4 +1064,9 @@ export default { vendors_transactions: 'Vendors Transactions', reference_type: 'Reference type', transaction_number: 'Transaction number', + cash_flow_statement: 'Cash Flow Statement', + statement_of_cash_flow: 'Statement of Cash Flow ', + inventory_item_details: 'Inventory Item Details', + congratulations: 'Congratulations', }; + diff --git a/client/src/lang/en/index.json b/client/src/lang/en/index.json new file mode 100644 index 000000000..2369d498e --- /dev/null +++ b/client/src/lang/en/index.json @@ -0,0 +1,1139 @@ +{ + "hello_world": "Hello World", + "email_or_phone_number": "Email or phone number", + "password": "Password", + "login": "Login", + "invalid_email_or_phone_number": "Invalid email or phone number.", + "required": "Required", + "reset_password": "Reset Password", + "the_user_has_been_suspended_from_admin": "The user has been suspended from the administrator.", + "email_and_password_entered_did_not_match": "The email and password you entered did not match our records.", + "field_name_must_be_number": "field_name_must_be_number", + "name": "Name", + "quick_find": "Quick find", + "date": "Date", + "description": "Description", + "from_date": "From date", + "to_date": "To date", + "report_date_range": "Report date range", + "log_in": "Log in", + "forget_my_password": "Forget my password", + "keep_me_logged_in": "Keep me logged in", + "create_an_account": "Create an account", + "need_bigcapital_account": "Need a Bigcapital account ?", + "show": "Show", + "hide": "Hide", + "an_unexpected_error_occurred": "An unexpected error occurred", + "welcome_to_bigcapital": "Welcome to Bigcapital", + "enter_your_personal_information": " Enter your personal information", + "first_name": "First Name", + "last_name": "Last Name", + "phone_number": "Phone Number", + "you_email_address_is": "You email address is", + "you_will_use_this_address_to_sign_in_to_bigcapital": "You will use this address to sign in to Bigcapital.", + "signing_in_or_creating": "By signing in or creating an account, you agree with our
    Terms & Conditions and Privacy Statement ", + "and": "And", + "create_account": "Create Account", + "success": "Success", + "register_a_new_organization": "Register a New Organization.", + "organization_name": "Organization Name", + "email": "Email", + "register": "Register", + "password_successfully_updated": "The Password for your account was successfully updated.", + "choose_a_new_password": "Choose a new password", + "you_remembered_your_password": "You remembered your password ?", + "new_password": "New Password", + "submit_new_password": "Submit new password", + "you_can_t_login": "You can’t login?", + "we_ll_send_a_recovery_link_to_your_email": "We’ll send a recovery link to your email.", + "send_reset_password_mail": "Send Reset Password Mail", + "return_to_log_in": "Return to log in", + "sub_account": "Sub account?", + "account_type": "Account Type", + "account_name": "Account Name", + "account_code": "Account Code", + "parent_account": "Parent Account", + "edit": "Edit", + "submit": "Submit", + "close": "Close", + "edit_account": "Edit Account", + "new_account": "New Account", + "edit_currency": "Edit Currency", + "delete_currency": "Delete Currency", + "new_currency": "New Currency", + "currency_name": "Currency Name", + "currency_code": "Currency Code", + "select_currency_code": "Select Currency Code", + "edit_exchange_rate": "Edit Exchange Rate", + "new_exchange_rate": "New Exchange Rate", + "delete_exchange_rate": "Delete Exchange Rate", + "exchange_rate": "Exchange Rate", + "edit_category": "Edit Category", + "delete_category": "Delete Category", + "new_category": "New Category", + "category_name": "Category Name", + "parent_category": "Parent Category", + "new": "New", + "invite_user": "Invite User", + "your_access_to_your_team": "Your teammate will get an email that gives them access to your team.", + "invite": "Invite", + "count": "Count", + "item_type": "Item Type", + "item_name": "Item Name", + "category": "Category", + "account": "Account", + "sales_information": "Sales Information", + "purchase_information": "Purchase Information", + "selling_price": "Selling Price", + "cost_price": "Cost Price", + "inventory_information": "Inventory Information", + "inventory_account": "Inventory Account", + "opening_quantity": "Opening quantity", + "opening_cost": "Opening cost", + "save": "Save", + "save_as_draft": "Save as Draft", + "active": "Active", + "draft": "Draft", + "published": "Published", + "new_item": "New Item", + "cost_price_": "Cost price", + "sell_price_": "Sell price", + "table_views": "Table Views", + "delete": "Delete", + "delete_count": "Delete ({count})", + "import": "Import", + "export": "Export", + "filter": "Filter", + "view_details": "View Details", + "edit_item": "Edit Item", + "delete_item": "Delete Item", + "sell_price": "Sell Price", + "cancel": "Cancel", + "move_to_trash": "Move to Trash", + "save_new": "Save & New", + "journal_number": "Journal Number", + "credit_currency": "Credit ({currency})", + "debit_currency": "Debit ({currency})", + "note": "Note", + "new_lines": "New lines", + "clear_all_lines": "Clear all lines", + "new_journal": "New Journal", + "publish_journal": "Publish Journal", + "edit_journal": "Edit Journal", + "delete_journal": "Delete Journal", + "amount": "Amount", + "journal_no": "Journal No.", + "status": "Status", + "created_at": "Created At", + "archive": "Archive", + "inactivate": "Inactivate", + "activate": "Activate", + "inactivate_account": "Inactivate Account", + "activate_account": "Activate Account", + "delete_account": "Delete Account", + "code": "Code", + "type": "Type", + "normal": "Normal", + "balance": "Balance", + "something_wrong": "Something wrong", + "filters": "Filters", + "add_order": "Add order", + "expense_account": "Expense Account", + "bulk_update": "Bulk Update", + "all_accounts": "All accounts", + "go_to_bigcapital_com": "← Go to bigcapital.com", + "currency": "Currency", + "new_conditional": "+ New Conditional", + "chart_of_accounts": "Chart of Accounts", + "exchange_rate_details": "Exchange Rate Details", + "exchange_rates_list": "Exchange Rates List", + "manual_journals": "Manual Journals", + "edit_expense_details": "Edit Expense Details", + "edit_category_details": "Edit Category Details", + "category_list": "Category List", + "edit_item_details": "Edit Item Details", + "items_list": "Items List", + "edit_custom_view": "Edit Custom View", + "new_custom_view": "New Custom View", + "view_name": "View Name", + "item": "Item", + "service_has_been_created_successfully": "{service} {name} has been created successfully.", + "service_has_been_edited_successfully": "{service} {name} has been edited successfully.", + "you_are_about_permanently_delete_this_journal": "You're about to permanently delete this journal and all its transactions on accounts and attachments, and all of its data.

    If you're not sure, you can archive this journal instead.", + "once_delete_these_accounts_you_will_not_able_restore_them": "Once you delete these accounts, you won't be able to retrieve them later. Are you sure you want to delete them?", + "once_delete_these_service_you_will_not_able_restore_it": "Once you delete these {service}, you won't be able to retrieve them later. Are you sure you want to delete this {service}?", + "you_could_not_delete_predefined_accounts": "You could't delete predefined accounts.", + "cannot_delete_account_has_associated_transactions": "You could't not delete account that has associated transactions.", + "the_account_has_been_successfully_inactivated": "The account has been successfully inactivated.", + "the_account_has_been_successfully_activated": "The account has been successfully activated.", + "the_account_has_been_successfully_deleted": "The account has been successfully deleted.", + "the_accounts_has_been_successfully_deleted": "The accounts have been successfully deleted.", + "are_sure_to_inactive_this_account": "Are you sure you want to inactive this account? You will be able to activate it later", + "are_sure_to_inactive_this_accounts": "Are you sure you want to inactive this accounts? You will be able to activate it later", + "are_sure_to_activate_this_account": "Are you sure you want to activate this account? You will be able to inactivate it later", + "are_sure_to_activate_this_accounts": "Are you sure you want to activate this accounts? You will be able to inactivate it later", + "once_delete_this_account_you_will_able_to_restore_it": "Once you delete this account, you won't be able to restore it later. Are you sure you want to delete this account?

    If you're not sure, you can inactivate this account instead.", + "the_journal_has_been_created_successfully": "The journal #{number} has been created successfully.", + "the_journal_has_been_edited_successfully": "The journal #{number} has been edited successfully.", + "the_journal_has_been_deleted_successfully": "The journal has been deleted successfully", + "the_manual_journal_has_been_published": "The manual journal has been published", + "the_journals_has_been_deleted_successfully": "The journals has been deleted successfully ", + "credit": "Credit", + "debit": "Debit", + "once_delete_this_item_you_will_able_to_restore_it": "Once you delete this item, you won't be able to restore the item later. Are you sure you want to delete ?

    If you're not sure, you can inactivate it instead.", + "the_item_has_been_deleted_successfully": "The item has been deleted successfully.", + "the_item_has_been_created_successfully": "The item has been created successfully.", + "the_item_has_been_edited_successfully": "The item #{number} has been edited successfully", + "the_item_category_has_been_created_successfully": "The item category has been created successfully.", + "the_item_category_has_been_edited_successfully": "The item category has been edited successfully.", + "the_item_category_has_been_deleted_successfully": "The item category has been deleted successfully.", + "once_delete_these_item_categories_you_will_not_able_restore_them": "Once you delete these categories, you won't be able to retrieve them later. Are you sure you want to delete them?", + "once_delete_these_views_you_will_not_able_restore_them": "Once you delete the custom view, you won't be able to restore it later. Are you sure you want to delete this view?", + "the_custom_view_has_been_deleted_successfully": "The custom view has been deleted successfully.", + "teammate_invited_to_organization_account": "Your teammate has been invited to the organization account.", + "select_account_type": "Select account type", + "menu": "Menu", + "table": "Table", + "logout": "Logout", + "select_expense_account": "Select Expense Account", + "or": "OR", + "comparator": "Comparator", + "equals": "Equals", + "not_equal": "Not Equal", + "contain": "Contain", + "not_contain": "Not Contain", + "cash": "Cash", + "accrual": "Accrual", + "from": "From", + "to": "To", + "accounting_basis": "Accounting basis:", + "general": "General", + "users": "Users", + "currencies": "Currencies", + "accountant": "Accountant", + "accounts": "Accounts", + "homepage": "Homepage", + "items": "Items", + "financial": "Financial", + "accounts_chart": "Accounts Chart", + "manual_journal": "Manual Journal", + "make_journal": "Make Journal", + "banking": "Banking", + "sales": "Sales", + "purchases": "Purchases", + "financial_reports": "Financial Reports", + "all_financial_reports": "All Financial Reports", + "balance_sheet": "Balance Sheet", + "trial_balance_sheet": "Trial Balance Sheet", + "journal": "Journal", + "general_ledger": "General Ledger", + "general_ledger_sheet": "General Ledger Sheet", + "profit_loss_sheet": "Profit/Loss Sheet", + "expenses": "Expenses", + "expenses_list": "Expenses List", + "new_expenses": "New Expenses", + "preferences": "Preferences", + "auditing_system": "Auditing System", + "all": "All", + "organization": "Organization.", + "check_your_email_for_a_link_to_reset": "Check your email for a link to reset your password.If it doesn’t appear within a few minutes, check your spam folder.", + "we_couldn_t_find_your_account_with_that_email": "We couldn't find your account with that email.", + "select_parent_account": "Select Parent Account", + "the_exchange_rate_has_been_edited_successfully": "The exchange rate has been edited successfully", + "the_exchange_rate_has_been_created_successfully": "The exchange rate has been created successfully", + "the_user_details_has_been_updated": "The user details has been updated", + "filters_applied": "filters applied", + "select_item_type": "Select Item Type", + "service": "Service", + "inventory": "Inventory", + "non_inventory": "Non-Inventory", + "select_category": "Select category", + "select_account": "Select Account", + "custom_fields": "Custom Fields", + "organization_industry": "Organization Industry", + "business_location": "Business Location", + "base_currency": "Base Currency", + "fiscal_year": "Fiscal Year", + "language": "Language", + "time_zone": "Time Zone", + "date_format": "Date Format", + "edit_user": "Edit User", + "edit_invite": "Edit Invite", + "inactivate_user": "Inactivate User", + "activate_user": "Activate User", + "delete_user": "Delete User", + "full_name": "Full Name", + "the_user_has_been_inactivated_successfully": "The user has been inactivated successfully.", + "the_user_has_been_deleted_successfully": "The user has been deleted successfully.", + "customize_report": "Customize Report", + "print": "Print", + "accounts_with_zero_balance": "Accounts with Zero Balance", + "all_transactions": "All Transactions", + "filter_accounts": "Filter accounts", + "calculate_report": "Calculate report", + "total": "Total", + "specific_accounts": "Specific Accounts", + "trans_num": "Trans. NUM", + "journal_sheet": "Journal Sheet", + "run_report": "Run Report", + "num": "Num.", + "inviting": "INVITING", + "acc_code": "Acc. Code", + "display_report_columns": "Display report columns", + "select_display_columns_by": "Select display columns by...", + "credit_and_debit_not_equal": "credit and debit not equal", + "the_currency_has_been_edited_successfully": "The currency has been edited successfully", + "the_currency_has_been_created_successfully": "The currency has been created successfully", + "the_currency_has_been_deleted_successfully": "The currency has been deleted successfully", + "once_delete_this_exchange_rate_you_will_able_to_restore_it": "Once you delete this exchange rate, you won't be able to restore it later. Are you sure you want to delete this exchange rate?", + "once_delete_these_exchange_rates_you_will_not_able_restore_them": "Once you delete these exchange rates, you won't be able to retrieve them later. Are you sure you want to delete them?", + "once_delete_this_item_category_you_will_able_to_restore_it": "Once you delete this category, you won't be able to restore it later. Are you sure you want to delete this item?", + "select_business_location": "Select Business Location", + "select_base_currency": "Select base currency", + "select_fiscal_year": "Select fiscal year", + "select_language": "Select Language", + "select_date_format": "Select Date Format", + "select_time_zone": "Select Time Zone", + "select_currency": "Select Currency", + "once_delete_this_currency_you_will_able_to_restore_it": "Once you delete this currency, you won't be able to restore it later. Are you sure you want to delete this item?", + "select_parent_category": "Select Parent Category", + "the_options_has_been_created_successfully": "The options has been created successfully", + "there_is_exchange_rate_in_this_date_with_the_same_currency": "There is exchange rate in this date with the same currency.", + "the_exchange_rates_has_been_deleted_successfully": "The exchange rates has been deleted successfully", + "once_delete_this_expense_you_will_able_to_restore_it": "Once you delete this expense, you won't be able to restore it later. Are you sure you want to delete this expense?", + "january": "January", + "february": "February", + "march": "March", + "april": "April", + "may": "May", + "june": "June", + "july": "July", + "august": "August", + "september": "September", + "october": "October", + "november": "November", + "december": "December", + "expense_account_id": "Expense account", + "payment_account_id": "Payment account", + "currency_code_": "Currency code", + "publish": "Publish", + "exchange_rate_": "Exchange rate", + "journal_number_": "Journal number", + "first_name_": "First name", + "last_name_": "Last name", + "phone_number_": "Phone number", + "organization_name_": "Organization name", + "confirm_password": "Confirm password", + "crediential": "Email or Phone number", + "account_type_id": "Account type", + "account_name_": "Account name", + "currency_name_": "Currency name", + "cost_account_id": "Cost account", + "sell_account_id": "Sell account", + "item_type_": "Item type", + "item_name_": "Item name", + "organization_industry_": "Organization industry", + "base_currency_": "Base currency", + "date_format_": "Date format", + "category_name_": "Category name", + "sell_account_": "Sell account", + "cost_account_": "Cost account", + "inventory_account_": "Inventory account", + "view_name_": "View name", + "time_zone_": "Time zone", + "location": "Location", + "the_items_has_been_deleted_successfully": "The items have been deleted successfully.", + "once_delete_these_items_you_will_not_able_restore_them": "Once you delete these items, you won't be able to retrieve them later. Are you sure you want to delete them?", + "ops_something_went_wrong": "Something went wrong! Please try again.", + "session_expired": "Session Expired!", + "this_report_does_not_contain_any_data_between_date_period": "This report does not contain any data between date period.", + "welcome_organization_account_has_been_created": "👋 Welcome, You organization account has been created, Sign in now!", + "the_phone_number_already_used_in_another_account": "the phone number is already used in another account", + "the_email_already_used_in_another_account": "The email is already used in another account", + "hide_filter": "Hide filter", + "show_filter": "Show filter", + "new_role": "New Role", + "quick_new": "Quick new", + "help": "Help", + "organization_id": "Orgnization ID", + "beneficiary": "Beneficiary", + "payment_date": "Payment Date", + "ref_no": "Ref No.", + "payment_account_": "Payment account", + "expense_category": "Expense Category", + "total_currency": "Total ({currency})", + "amount_currency": "Amount ({currency})", + "publish_expense": "Publish Expense", + "edit_expense": "Edit Expense", + "delete_expense": "Delete Expense", + "new_expense": "New Expense", + "full_amount": "Full Amount", + "payment_date_": "Payment date", + "the_expense_has_been_created_successfully": "The expense #{number} has been created successfully.", + "the_expense_has_been_edited_successfully": "The expense #{number} has been edited successfully.", + "the_expense_has_been_deleted_successfully": "The expense has been deleted successfully", + "the_expenses_have_been_deleted_successfully": "The expenses have been deleted successfully", + "once_delete_these_expenses_you_will_not_able_restore_them": "Once you delete these expenses, you won't be able to retrieve them later. Are you sure you want to delete them?", + "the_expense_has_been_published": "The expense has been published", + "select_customer": "Select customer", + "total_amount_equals_zero": "Total amount equals zero", + "value": "Value", + "you_reached_conditions_limit": "You have reached to conditions limit.", + "customer_name": "Customer Name", + "as_date": "As Date", + "aging_before_days": "Aging before days", + "aging_periods": "Aging periods", + "name_": "name", + "as": "As", + "receivable_aging_summary": "Receivable Aging Summary", + "AR_Aging_Summary": "A/R Aging Summary", + "AP_Aging_Summary": "A/P Aging Summary", + "customers": "Customers", + "new_customers": "New Customers", + "customer_type_": "Customer type", + "display_name_": "Display name", + "new_customer": "New Customer", + "customer_type": "Customer Type", + "customer_account": "Customer Account", + "business": "Business", + "individual": "Individual", + "display_name": "Display Name", + "the_customer_has_been_created_successfully": "The customer has been created successfully.", + "select_contact": "Select contact", + "contact": "Contact", + "contacts": "Contacts", + "close_sidebar": "Close sidebar.", + "open_sidebar": "Open sidebar.", + "recalc_report": "Re-calc Report", + "remove_the_line": "Remove the line", + "no_results": "No results.", + "all_reports": "All Reports", + "next": "Next", + "previous": "Previous", + "showing_current_page_to_total": "Showing {currentPage} to {totalPages} of {total} entries", + "new_child_account": "New Child Account", + "contact_name": "Contact Name", + "company_name": "Company Name", + "other": "Other", + "address": "Address", + "attachement": "Attachement", + "country": "Country", + "city_town": "City/Town", + "state": "State", + "zip_code": "ZIP/Code", + "streat": "Streat", + "edit_customer": "Edit Customer", + "delete_customer": "Delete Customer", + "billing_address": "Billing Address", + "shipping_address": "Shipping Address", + "customers_list": "Customers List", + "receivable_balance": "Receivable balance", + "the_customer_has_been_deleted_successfully": "The customer has been deleted successfully.", + "the_customers_has_been_deleted_successfully": "The customers have been deleted successfully.", + "the_item_customer_has_been_edited_successfully": "The item customer has been edited successfully.", + "once_delete_this_customer_you_will_able_to_restore_it": "Once you delete this customer, you won't be able to restore it later. Are you sure you want to delete this cusomter?", + "once_delete_these_customers_you_will_not_able_restore_them": "Once you delete these customers, you won't be able to retrieve them later. Are you sure you want to delete them?", + "after": "After", + "before": "Before", + "count_filters_applied": "{count} filters applied", + "is": "Is", + "is_not": "Is Not", + "create_a_new_view": "Create a new view", + "in": "In", + "not_equals": "Not Equals", + "select_journal_type": "Select journal type", + "journal_type": "Journal Type", + "journal_reference_hint": "A unique reference for this journal. It is limited to 10 characters and can comprise of letters, digitals and underscore.", + "contact_column_hint": "Contact column to record receivable and payable to customer or vendor.", + "make_journal_entry": "Make Journal Entry", + "journal_number_is_already_used": "Journal number is already used.", + "account_code_hint": "A unique code/number for this account (limited to 10 characters)", + "logic_expression": "logic expression", + "assign_to_customer": "Assign to Customer", + "inactive": "Inactive", + "should_select_customers_with_entries_have_receivable_account": "Should select customers with entries that have receivable account.", + "should_select_vendors_with_entries_have_payable_account": "Should select vendors with entries that have payable account.", + "vendors_should_selected_with_payable_account_only": "Vendors contacts should selected with payable account only.", + "customers_should_selected_with_receivable_account_only": "Customers contacts should selected with receivable account only.", + "amount_cannot_be_zero_or_empty": "Amount cannot be zero or empty.", + "should_total_of_credit_and_debit_be_equal": "Should total of credit and debit be equal.", + "no_accounts": "No Accounts", + "the_accounts_have_been_successfully_inactivated": "The accounts have been successfully inactivated.", + "account_code_is_not_unique": "Account code is not unqiue.", + "are_sure_to_publish_this_expense": "Are you sure you want to publish this expense?", + "once_delete_these_journals_you_will_not_able_restore_them": "Once you delete these journalss, you won't be able to retrieve them later. Are you sure you want to delete them?", + "once_delete_this_journal_you_will_able_to_restore_it": "Once you delete this journal, you won't be able to restore it later. Are you sure you want to delete ?", + "the_expense_is_already_published": "The expense is already published.", + "accounts_without_zero_balance": "Accounts without zero-balance", + "accounts_with_transactions": "Accounts with transactions", + "include_accounts_once_has_transactions_on_given_date_period": "Include accounts that onces have transactions on the given date period only.", + "include_accounts_and_exclude_zero_balance": "Include accounts and exclude that ones have zero-balance.", + "all_accounts_including_with_zero_balance": "All accounts, including that ones have zero-balance.", + "notifications": "Notifications", + "you_could_not_delete_account_has_child_accounts": "You could not delete account has child accounts.", + "journal_entry": "Journal Entry", + "estimate": "Estimate #", + "estimate_date": "Estimate Date", + "expiration_date": "Expiration Date", + "customer_note": "Customer Note", + "select_customer_account": "Select Customer Account", + "select_product": "Select Product", + "reference": "Reference #", + "clear": "Clear", + "save_send": "Save & Send", + "estimates": "Estimates", + "edit_estimate": "Edit Estimate", + "delete_estimate": "Delete Estimate", + "new_estimate": "New Estimate", + "customer_name_": "Customer name", + "estimate_date_": "Estimate date", + "expiration_date_": "Expiration date", + "estimate_number_": "Estimate number", + "discount": "Discount %", + "quantity": "Quantity", + "rate": "Rate", + "estimates_list": "Estimates List", + "estimate_number": "Estimate Number", + "product_and_service": "Product/Service", + "the_estimate_has_been_edited_successfully": "The estimate #{number} has been edited successfully.", + "the_estimate_has_been_created_successfully": "The estimate #{number} has been created successfully.", + "the_estimate_has_been_deleted_successfully": "The estimate has been deleted successfully.", + "once_delete_this_estimate_you_will_able_to_restore_it": "Once you delete this estimate, you won't be able to restore it later. Are you sure you want to delete this estimate?", + "cannot_be_zero_or_empty": "cannot be zero or empty.", + "invoices": "Invoices", + "invoices_list": "Invoices List", + "invoice_date_": "Invoice date", + "invoice_no": "Invoice #", + "invoice_no__": "Invoice No", + "invoice_no_": "Invoice number", + "due_date_": "Due date", + "invoice_message": "Invoice Message", + "reference_no": "Reference No", + "invocie_number": "Invoice Number", + "amount_due": "Amount Due", + "payment_amount": "Payment Amount", + "edit_invoice": "Edit Invoice", + "delete_invoice": "Delete Invoice", + "new_invoice": "New Invoice", + "invoice_list": "Invoice List", + "the_invoice_has_been_edited_successfully": "The invoice #{number} has been edited successfully.", + "the_invoice_has_been_created_successfully": "The invoice #{number} has been created successfully.", + "the_invoice_has_been_deleted_successfully": "The invoice has been deleted successfully.", + "once_delete_this_invoice_you_will_able_to_restore_it": "Once you delete this invoice, you won't be able to restore it later. Are you sure you want to delete this invoice?", + "receipts_list": "Receipts List", + "receipts": "Receipts", + "receipt": "Receipt #", + "receipt_date_": "Receipt date", + "receipt_date": "Receipt Date", + "deposit_account_": "Deposit account", + "receipt_message_": "Receipt message", + "receipt_no_": "receipt number", + "edit_receipt": "Edit Receipt", + "delete_receipt": "Delete Receipt", + "new_receipt": "New Receipt", + "receipt_message": "Receipt Message", + "statement": "Statement", + "deposit_account": "Deposit Account", + "send_to_email": "Send to email", + "select_deposit_account": "Select Deposit Account", + "once_delete_this_receipt_you_will_able_to_restore_it": "Once you delete this receipt, you won't be able to restore it later. Are you sure you want to delete this receipt?", + "the_receipt_has_been_created_successfully": "The receipt #{number} has been created successfully.", + "the_receipt_has_been_edited_successfully": "The receipt #{number} has been edited successfully.", + "the_receipt_has_been_deleted_successfully": "The receipt has been deleted successfully.", + "bills_list": "Bills List", + "bills": "Bills", + "accept": "Accept", + "vendor_name": "Vendor Name", + "select_vendor_account": "Select Vendor Account", + "bill_date": "Bill Date", + "due_date": "Due Date", + "bill_number": "Bill Number", + "edit_bill": "Edit Bill", + "new_bill": "New Bill", + "bill_date_": "Bill date", + "bill_number_": "Bill number", + "delete_bill": "Delete Bill", + "the_bill_has_been_edited_successfully": "The bill #{number} has been edited successfully.", + "the_bill_has_been_created_successfully": "The bill #{number} has been created successfully.", + "the_bill_has_been_deleted_successfully": "The bill has been deleted successfully.", + "once_delete_this_bill_you_will_able_to_restore_it": "Once you delete this bill, you won't be able to restore it later. Are you sure you want to delete this bill?", + "deposit_to": "Deposit to", + "edit_payment_receive": "Edit Payment Receive", + "delete_payment_receive": "Delete Payment Receive", + "payment_receives_list": "Payment Receives List", + "payment_receive": "Payment Receive", + "new_payment_receive": "New Payment Receive", + "payment_receives": "Payment Receives", + "payment_receive_no": "Payment Receive #", + "payment_receive_no_": "Payment receive no", + "receive_amount": "Receive Amount", + "receive_amount_": "Receive amount", + "the_payment_receive_transaction_has_been_created": "The payment receive transaction has been created successfully.", + "the_payment_receive_has_been_deleted_successfully": "The payment receive has been deleted successfully.", + "the_payment_receive_transaction_has_been_edited": "The payment receive transaction has been edited successfully.", + "once_delete_this_payment_receive_you_will_able_to_restore_it": "Once you delete this payment receive, you won't be able to restore it later. Are you sure you want to delete this payment receive?", + "select_invoice": "Select Invoice", + "payment_mades": "Payment Mades", + "subscription": "Subscription", + "plan_slug": "Plan slug", + "billing": "Billing", + "the_billing_has_been_created_successfully": "The billing has been created successfully.", + "select_a_plan": "Select a plan", + "choose_your_billing": "Choose your billing", + "payment_methods": "Payment methods", + "usage": "Usage", + "basic": "Basic", + "license": "License", + "credit_card": "Credit Card", + "paypal": "Paypal", + "pro": "PRO", + "monthly": "Monthly", + "yearly": "Yearly", + "license_code": "License Code", + "year": "Year", + "please_enter_your_preferred_payment_method": "Please enter your preferred payment method below. You can use a credit / debit card or prepay through PayPal. ", + "cards_will_be_charged": "Cards will be charged either at the end of the month or whenever your balance exceeds the usage threshold. All major credit / debit cards accepted.", + "license_number": "License number", + "subscribe": "Subscribe", + "year_per": "year", + "payment_made": "Payment Made", + "edit_payment_made": "Edit Payment Made", + "delete_payment_made": "Delete Payment Made", + "payment_number": "Payment Number", + "vendor_name_": "Vendor name", + "bill_amount": "Bill Amount", + "payment_no_": "Payment number", + "new_payment_made": "New Payment Made", + "payment_made_list": "Payment Made List", + "payment_account": "Payment Account", + "select_vender_account": "Select Vender Account", + "select_payment_account": "Select Payment Account", + "the_payment_made_has_been_edited_successfully": "The payment made has been edited successfully.", + "the_payment_made_has_been_created_successfully": "The payment made has been created successfully.", + "the_payment_made_has_been_deleted_successfully": "The payment made has been deleted successfully.", + "once_delete_this_payment_made_you_will_able_to_restore_it": "Once you delete this payment made, you won't be able to restore it later. Are you sure you want to delete this payment made?", + "sellable": "Sellable", + "purchasable": "Purchasable", + "sell_account": "Sell Account", + "cost_account": "Cost Account", + "register_a_new_organization_now": "Register a New Organization now!.", + "you_have_a_bigcapital_account": "You have a Bigcapital account ", + "contact_us_technical_support": "Contact us - Technical Support", + "let_s_get_started": "Let’s Get Started", + "tell_the_system_a_little_bit_about_your_organization": "Tell the system a little bit about your organization.", + "organization_details": "Organization details", + "financial_starting_date": "Financial starting date ", + "note_you_can_change_your_preferences_later_in_dashboard_if_needed": "Note: You can change your preferences later in dashboard, if needed.", + "save_continue": "Save & Continue", + "organization_register": "Organization Register", + "fiscal_year_": "Fiscal year", + "welcome": "Welcome ", + "sign_out": "Sign out", + "we_re_here_to_help": "We’re Here to Help!", + "date_start_": "Date start", + "something_wentwrong": "Something went wrong.", + "license_code_": "License code", + "legal_organization_name": "Legal Organization Name", + "smaller_than": "Smaller than", + "smaller_or_equals": "Smaller or equals", + "bigger_than": "Bigger than", + "bigger_or_equals": "Bigger or equals", + "prefix": "Prefix", + "next_number": "Next Number", + "journal_number_settings": "Journal Number Settings", + "bill_number_settings": "Bill Number Settings", + "payment_number_settings": "Payment Number Settings", + "Estimate_number_settings": "Estimate Number Settings", + "receipt_number_settings": "Receipt Number Settings", + "invoice_number_settings": "Invoice Number Settings", + "receipt_number": "Receipt Number", + "estimate_number_is_not_unqiue": "Estimate number is not unqiue", + "invoice_number_is_not_unqiue": "Invoice number is not unqiue", + "sale_receipt_number_not_unique": "Receipt number is not unique", + "sale_invoice_number_is_exists": "Sale invoice number is exists", + "bill_number_exists": "Bill number exists", + "ok": "Ok!", + "quantity_cannot_be_zero_or_empty": "Quantity cannot be zero or empty.", + "customer_email": "Customer email", + "customer_phone_number": "Customer phone number", + "opening_balance_at": "Opening balance at", + "opening_balance": "Opening balance", + "balance_currency": "Balance currency", + "financial_details": "Financial details", + "are_you_sure_you_want_to_clear_this_transaction": "Are you sure you want to clear this transaction?", + "clearing_the_table_lines_will_delete_all_credits": "Clearing the table lines will delete all credits and payment were applied, Is this okay?", + "changing_full_amount_will_change_all_credits_and_payment": " Changing full amount will change all credits and payment were applied, Is this okay?", + "address_line_1": "Address line 1", + "address_line_2": "Address line 2", + "website": "Website", + "notes": "Notes", + "i_purchase_this_item": "I purchase this item from a vendor.", + "i_sell_this_item": "I sell this item to a customer.", + "select_display_name_as": "Select display name as", + "opening_date": "Opening date", + "item_code": "Item code", + "quantity_on_hand": "Quantity on hand", + "average_rate": "Average rate", + "the_name_used_before": "The name is already used.", + "the_item_has_associated_transactions": "The item has associated transactions.", + "customer_has_sales_invoices": "Cannot delete customer has associated sales invoices.", + "account_name_is_already_used": "Account name is already used.", + "vendors": "Vendors", + "vendor_email": "Vendor Email", + "new_vendor": "New Vendor", + "edit_vendor": "Edit Vendor", + "delete_vendor": "Delete Vendor", + "vendors_list": "Vendors List", + "the_vendor_has_been_created_successfully": "The vendor has been successfully created.", + "the_vendor_has_been_deleted_successfully": "The vendor has been deleted successfully.", + "the_item_vendor_has_been_edited_successfully": "The item vendor has been edited successfully.", + "once_delete_this_vendor_you_will_able_to_restore_it": "Once you delete this vendor, you won't be able to restore it later. Are you sure you want to delete this vendor?", + "once_delete_these_vendors_you_will_not_able_restore_them": "Once you delete these vendors, you won't be able to retrieve them later. Are you sure you want to delete them?", + "vendor_has_bills": "Vendor has bills", + "you_cannot_make_payment_with_zero_total_amount": "You cannot record payment transaction with zero total amount", + "are_sure_to_publish_this_manual_journal": "Are you sure you want to publish this manual journal?", + "save_publish": "Save and Publish", + "publish_and_new": "Publish and new", + "publish_continue_editing": "Publish (continue editing)", + "save_and_new": "Save and new", + "save_continue_editing": "Save (continue editing)", + "reset": "Reset ", + "save_and_send": "Save and Send", + "posting_date": "Posting date", + "customer": "Customer", + "email_is_already_used": "The email is already used.", + "the_item_categories_has_been_deleted_successfully": "The item categories has been deleted successfully .", + "receivable_accounts_should_assign_with_customers": "Receivable accounts should assign with customers.", + "delivered": "Delivered", + "save_and_deliver": "Save & Deliver", + "deliver_and_new": "Deliver and new", + "deliver_continue_editing": "Deliver (continue editing)", + "due_in": "Due in {due} day.", + "day_partially_paid": "Partially paid, {due} due.", + "overdue_by": "Overdue by {overdue} day.", + "paid": "Paid", + "your_account_has_been_locked": "Your account has been locked due to repeated failed login attempts. Please wait a few minutes before trying again.", + "the_invoice_has_been_delivered_successfully": "The invoice has been delivered successfully.", + "are_sure_to_deliver_this_invoice": "Are you sure you want to deliver this invoice?", + "mark_as_delivered": "Mark as delivered", + "deliver": "Deliver", + "mark_as_closed": "Mark as closed", + "mark_as_opened": "Mark as opened", + "save_close": "Save & Close", + "save_open": "Save & Open", + "close_and_new": "Close and new", + "close_continue_editing": "Close (continue editing)", + "the_receipt_has_been_closed_successfully": "The receipt has been closed successfully.", + "are_sure_to_close_this_receipt": "Are you sure you want to close this receipt?", + "closed": "Closed", + "open_and_new": "Open and new", + "open_continue_editing": "Open (continue editing)", + "the_bill_has_been_opened_successfully": "The bill has been opened successfully.", + "open": "Open", + "are_sure_to_open_this_bill": "Are you sure you want to open this bill?", + "opened": "Opened", + "the_estimate_has_been_delivered_successfully": "The estimate has been delivered successfully.", + "the_estimate_has_been_approved_successfully": "The estimate has been approved successfully.", + "the_estimate_has_been_rejected_successfully": "The estimate has been rejected successfully.", + "are_sure_to_deliver_this_estimate": "Are you sure you want to deliver this estimate?", + "approve": "Approve", + "are_sure_to_approve_this_estimate": "Are you sure you want to approve this estimate?", + "reject": "Reject", + "are_sure_to_reject_this_estimate": "Are you sure you want to reject this estimate?", + "mark_as_approved": "Mark as approved", + "mark_as_rejected": "Mark as rejected", + "rejected": "Rejected", + "approved": "Approved", + "the_item_has_been_inactivated_successfully": "The item has been inactivated successfully.", + "the_item_has_been_activated_successfully": "The item has been activated successfully.", + "are_sure_to_inactive_this_item": "Are you sure you want to inactive this item? You will be able to activate it later", + "are_sure_to_activate_this_item": "Are you sure you want to activate this item? You will be able to inactivate it later", + "inactivate_item": "Inactivate Item", + "activate_item": "Activate Item", + "all_payments": "All Payments", + "hide_customizer": "Hide Customizer", + "opening_quantity_": "Opening quantity", + "opening_average_cost": "Opening average cost", + "opening_cost_": "Opening cost ", + "opening_date_": "Opening date ", + "the_invoice_cannot_be_deleted": "The invoice cannot be deleted cause has associated payment transactions", + "category_name_exists": "Category name exists", + "some_customers_have_sales_invoices": "Some customers have sales invoices", + "inventory_adjustments": "Inventory adjustments", + "make_adjustment": "Make a adjustment", + "adjustment_type": "Adjustment type", + "decrement": "Decrement", + "new_quantity": "New quantity", + "reason": "Reason", + "increment": "Increment", + "cost": "Cost", + "qty_on_hand": "Qty on hand", + "adjustment_account": "Adjustment account", + "inventory_adjustment_list": "Inventory Adjustment List", + "delete_adjustment": "Delete Adjustment", + "the_make_adjustment_has_been_created_successfully": "The make adjustment has been created successfully.", + "the_adjustment_has_been_deleted_successfully": "The adjustment has been deleted successfully.", + "once_delete_this_inventory_a_adjustment_you_will_able_to_restore_it": "Once you delete this inventory a adjustment, you won't be able to restore it later. Are you sure you want to delete this invoice?", + "select_adjustment_account": "Select adjustment account", + "qty": "Quantity on hand", + "money_format": "Money format", + "show_zero": "Show zero.", + "show_negative_in_red": "Show negative in red.", + "divide_on_1000": "Divide on 1000.", + "negative_format": "Negative format", + "decimal_places": "Decimal places", + "run": "Run", + "you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions": "You could not delete item that has associated inventory adjustments transactions", + "format": "Format", + "current": "Current", + "adjustment_reasons": "Adjustment reasons", + "specific_customers": "Specific Customers", + "all_customers": "All Customers", + "all_vendors": "All Vendors", + "selected_customers": "{count} Selected Customers", + "transaction_number": "Transaction #", + "running_balance": "Running balance", + "view_all": "View all", + "payment_via_voucher": "Payment via voucher", + "voucher_number": "Voucher number", + "voucher": "Voucher", + "payment_number_is_not_unique": "Payment number is not unique.", + "change_full_amount": "Change full amount", + "view_paper": "View Paper", + "estimate_paper": "Estimate Paper", + "invoice_paper": "Invoice Paper", + "receipt_paper": "Receipt Paper", + "payable_aging_summary": "Payable Aging Summary", + "payment_receive_paper": "Payment Receive Paper", + "specific_vendors": "Specific Vendors", + "accounts_receivable_a_r": "Accounts Receivable A/R", + "accounts_payable_a_p": "Accounts Payable A/P", + "financial_accounting": " Financial Accounting", + "products_services_inventory": "Products,Services & Inventory", + "payable_a_p": "Payable A/P", + "keyboard_shortcuts": "Keyboard Shortcuts", + "shortcut_keys": "Shortcut Keys", + "oK_": "Ok", + "convert_to_invoice": "Convert to Invoice", + "sale_estimate_is_already_converted_to_invoice": "Sale estimate is already converted to invoice.", + "duplicate": "Duplicate", + "are_you_sure_want_to_duplicate": "Are you sure want to duplicate this contact, which contact type?", + "contact_type": "Contact Type", + "duplicate_contact": "Duplicate Contact", + "contact_type_": "Contact type", + "the_payment_amount_that_received": "The payment amount that received from the customer is more than the due amount for this invoice.", + "invoice_number": "Invoice number", + "invoice_date": "Invoice date", + "invoice_amount": "Invoice amount", + "make_payment": "Make Payment", + "add_payment": "Add Payment", + "quick_receive_payment": "Quick Receive Payment", + "amount_received": "Amount Received", + "payment_no": "Payment No.", + "payment_receive_number_required": "Payment receive number required", + "quick_made_payment": "Quick Made Payment", + "the_payment_amount_bigger_than_invoice_due_amount": "The payment amount bigger than invoice due amount.", + "accounting_basis_": "Accounting Basis", + "deposit_customer_account": "Deposit customer account", + "withdrawal_vendor_account": "Withdrawal vendor account", + "customer_advance_deposit": "Customer advance deposit", + "cannot_delete_bill_that_has_payment_transactions": "Cannot delete bill that has associated payment transactions.", + "cannot_change_item_type_to_inventory_with_item_has_associated_transactions": "Cannot change item type to inventory with item has associated transactions.", + "work_phone": "Work Phone", + "cannot_delete_vendor_that_has_associated_purchase_bills": "Cannot delete vendor that has associated purchase bills.", + "the_accountant_preferences_has_been_saved": "The accountant preferences has been saved.", + "the_items_preferences_has_been_saved": "The items preferences has been saved.", + "preferred_sell_account": "Preferred sell account", + "preferred_cost_account": "Preferred cost account", + "preferred_inventory_account": "Preferred inventory account", + "this_customer_cannot_be_deleted_as_it_is_associated_with_transactions": "This customer cannot be deleted as it is associated with transactions.", + "this_vendor_cannot_be_deleted_as_it_is_associated_with_transactions": "This vendor cannot be deleted as it is associated with transactions.", + "currency_sign": "Currency sign", + "cannot_change_item_inventory_account": "Cannot change item inventory account while the item has transactions.", + "purchases_by_items": "Purchases by items", + "quantity_purchased": "Quantity purchased", + "purchase_amount": "Purchase amount", + "average_price": "Average price", + "sales_by_items": "Sales by items", + "sold_quantity": "Sold quantity", + "sold_amount": "Sold amount", + "asset_value": "Asset value", + "average": "Average", + "inventory_valuation": "Inventory valuation", + "payable_accounts_should_assign_with_vendors": "Payable accounts should assign with vendors.", + "account_paper": "Account Paper", + "transaction_date": "Transaction date", + "transaction_type": "Transaction type", + "account_normal": "Account normal", + "published_at": "Published at", + "customers_balance_summary": "Customers Balance Summary", + "vendors_balance_summary": "Vendors Balance Summary", + "percentage_of_column": "Percentage", + "customers_transactions": "Customers Transactions", + "vendors_transactions": "Vendors Transactions", + "reference_type": "Reference type", + "transaction_number": "Transaction number", + "cash_flow_statement": "Cash Flow Statement", + "statement_of_cash_flow": "Statement of Cash Flow ", + "inventory_item_details": "Inventory Item Details", + "sales_invoices": "Sales invoices", + "tracking_sales_invoices_with_your_customers": "Tracking sales invoices with your customers with payment due date.", + "sales_estimates": "Sales estimates", + "manage_your_sales_estimates_to_create_quotes": "Manage your sales estimates to create quotes that can later be turned to a sale invoice.", + "sales_receipts": "Sales receipts", + "manage_sales_receipts_for_sales_that_get_paid": "Manage sales receipts for sales that get paid immediately from the customer.", + "manage_the_customers_relations_with_customer": "Manage the customers relations with customer receivable and credit balances.", + "customers_payment": "Customers payment", + "manage_payment_transactions_from_your_customers": "Manage payment transactions from your customers with sale invoices.", + "purchase_invoices": "Purchase invoices", + "manage_the_purchase_invoices_with_your_vendors": "Manage the purchase invoices with your vendors with payment due date.", + "manage_the_vendors_relations_with_vendor_relations": "Manage the vendors relations with vendor payable and debit balances.", + "vendors_payments": "Vendors payments", + "manage_payments_transactions_to_your_vendors": "Manage payments transactions to your vendors with purchase invoices.", + "manage_your_accounts_chart_to_record_your_transactions_and_categories": "Manage your accounts chart to record your transactions and categories your transactions in parent accounts.", + "manage_manual_journal_transactions_on_accounts": "Manage manual journal transactions on accounts, cost centra and projects.", + "track_your_indirect_expenses_under_specific_categories": "Track your indirect expenses under specific categories such as payroll, rent.", + "financial_statements": "Financial statements", + "show_financial_reports_about_your_organization": "Show financial reports about your organization to summarize your business’s financial performance.", + "products_services": "Products & Services", + "manage_your_products_inventory_or_non_inventory": "Manage your products (inventory or non-inventory) and services and place them into categories.", + "products_services_categories": "Products & Services Categories", + "group_your_products_and_service": "Group your products and service into different categories.", + "manage_your_inventory_adjustment_of_inventory_items": "Manage your inventory adjustment of inventory items.", + "page_size": "Page size", + "there_is_no_items_categories_in_table_yet": "There is no items categories in table yet.", + "sales_inventory": "Sales & inventory", + "accounting": "Accounting", + "system": "SYSTEM", + "it_s_time_to_send_estimates_to_your_customers": "It's time to send estimates to your customers", + "it_is_a_long_established_fact_that_a_reader": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.", + "new_sale_estimate": "New sale estimate", + "learn_more": "Learn more", + "back_to_list": "Back to list.", + "estimate_details": "Estimate details", + "attachments_maximum": "Attachments: Maximum size: 20MB", + "drag_drop_files_here_or_click_here": "Drag/Drop files here or click here.", + "enter_an_item": "Enter an item...", + "i_will_enter_them_manually_each_time": "I will enter them manually each time", + "manual_entering_for_this_transaction": "Manual entering for this transaction.", + "due_amount": "Due Amount", + "invoice_details": "Invoice details", + "setting_your_auto_generated_estimate_number": "Setting your auto-generated estimate number", + "setting_your_auto_generated_journal_number": "Setting your auto-generated journal number", + "setting_your_auto_generated_invoice_number": "Setting your auto-generated invoice number", + "setting_your_auto_generated_payment_receive_number": "Setting your auto-generated Payment Receive number", + "the_organization_doesn_t_receive_money_yet": "The organization doesn't receive money, yet!", + "there_is_no_receivable_invoices_for_this_customer": "There is no receivable invoices for this customer that can be applied for this payment", + "please_select_a_customer_to_display_all_open_invoices_for_it": "Please select a customer to display all open invoices for it.", + "payment_receive_details": "Payment receive details", + "receive_full_amount": "Receive full amount", + "manage_the_organization_s_services_and_products": "Manage the organization’s services and products.", + "here_a_list_of_your_organization_products_and_services": " Here a list of your organization products and services, to be used when you create invoices or bills to your customers or vendors.", + "receipt_details": "Receipt details", + "bill_details": "Bill details", + "new_bill_payment": "New bill payment", + "new_sale_invoice": " New sale invoice", + "there_is_no_payable_bills_for_this_vendor_that_can_be_applied_for_this_payment": "There is no payable bills for this vendor that can be applied for this payment", + "please_select_a_vendor_to_display_all_open_bills_for_it": "Please select a vendor to display all open bills for it.", + "payment_made_details": "Payment made details", + "there_is_no_inventory_adjustments_transactions_yet": "There is no inventory adjustments transactions yet.", + "create_and_manage_your_organization_s_customers": "Create and manage your organization's customers.", + "salutation": "Salutation", + "work": "work", + "mobile": "Mobile", + "phone": "Phone", + "create_and_manage_your_organization_s_vendors": "Create and manage your organization's vendors.", + "balance_sheet_report": "Balance Sheet Report", + "reports_a_company_s_assets_liabilities_and_shareholders": "Reports a company's assets, liabilities and shareholders' equity at a specific point in time with comparison period(s).", + "summarizes_the_credit_and_debit_balance_of_each_account": "Summarizes the credit and debit balance of each account in your chart of accounts at a specific point in time.", + "profit_loss_report": "Profit/Loss Report", + "reports_the_revenues_costs_and_expenses": "Reports the revenues, costs and expenses incurred during a specific point in time with comparison period(s).", + "reports_inflow_and_outflow_of_cash_and_cash_equivalents": "Reports inflow and outflow of cash and cash equivalents between a specific two points of time.", + "journal_report": "Journal Report", + "the_debit_and_credit_entries_of_system_transactions": "The debit and credit entries of system transactions, sorted by date.", + "general_ledger_report": "General Ledger Report", + "reports_every_transaction_going_in_and_out_of_your": "Reports every transaction going in and out of your accounts and organized by accounts and date to monitoring activity of accounts.", + "summarize_total_unpaid_balances_of_customers_invoices": "Summarize total unpaid balances of customers invoices with number of days the unpaid invoice is overdue.", + "summarize_total_unpaid_balances_of_vendors_purchase": "Summarize total unpaid balances of vendors purchase invoices with the number of days the unpaid invoice is overdue.", + "sales_purchases_reports": "Sales/Purchases Reports", + "reports_every_transaction_going_in_and_out_of_your_items": "Reports every transaction going in and out of your items to monitoring activity of items.", + "reports_every_transaction_going_in_and_out_of_each_vendor_supplier": "Reports every transaction going in and out of each vendor/supplier.", + "reports_every_transaction_going_in_and_out_of_each_customer": "Reports every transaction going in and out of each customer.", + "summerize_the_total_amount_your_business_owes_each_vendor": "Summarize the total amount your business owes each vendor.", + "summerize_the_total_amount_of_each_customer_owes_your_business": "Summarize the total amount of each customer owes your business.", + "summarize_the_business_s_purchase_items_quantity_cost_and_average": "Summarize the business’s purchase items quantity, cost and average cost rate of each item during a specific point in time.", + "summarize_the_business_s_sold_items_quantity_income_and_average_income_rate": "Summarize the business’s sold items quantity, income and average income rate of each item during a specific point in time.", + "shows_the_average_age_of_unresolved_issues_for_a_project_or_filter": "Shows the average age of unresolved issues for a project or filter. This helps you see whether your backlog is being kept up to date", + "categories": "Categories", + "duplicate_item": "Duplicate item", + "summerize_your_transactions_for_each_inventory_item": "Summarize your transactions for each inventory item and how they affect quantity, valuation and weighted average.", + "summerize_how_much_each_customer_owes_your_business": "Summarize how much each customer owes your business.", + "duplicate_customer": "Duplicate customer", + "duplicate_vendor": "Duplicate vendor", + "new_billing": "New Billing", + "shown_on_sales_forms_and_purchase_orders": "Shown on sales forms and purchase orders.", + "for_reporting_you_can_specify_any_month": "For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).", + "you_can_t_change_the_base_currency_as_there_are_transactions": "You can't change the base currency as there are transactions recorded in your organization.", + "make_account_code_required_when_create_a_new_accounts": "Make account code required when create a new accounts.", + "should_account_code_be_unique_when_create_a_new_account": "Should account code be unique when create a new account.", + "select_a_preferred_account_to_deposit_into_it_after_customer_make_payment": "Select a preferred account to deposit into it after customer make payment.", + "select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits": "Select a preferred account to deposit into it vendor advanced deposits.", + "roles": "Roles", + "closing_balance": "Closing Balance", + "view_more_transactions": "View more transactions.", + "there_is_no_results_in_the_table": "There is no results in the table.", + "create_your_first_journal_entries_on_accounts_chart": "Create your first journal entries on accounts chart.", + "journal_details": "Journal details", + "create_and_manage_your_organization_s_expenses": "Create and manage your organization's expenses", + "expense_amount": "Expense Amount", + "expense_details": "Expense details", + "sub_total": "Sub Total", + "estimate_": "Estimate", + "billed_to": "Billed to", + "estimate_no": "Estimate No.", + "billed_from": "Billed from", + "estimate_amount": "Estimate amount", + "invoice": "Invoice", + "receipt_": "Receipt", + "receipt_no": "Receipt No.", + "receipt_amount": "Receipt amount", + "please_enter_your_preferred_payment_method_below": "Please enter your preferred payment method below.", + "submit_voucher": "Submit Voucher", + "sale_invoice": "Sale invoice", + "purchase_invoice": "Purchase invoice", + "expense": "Expense", + "vendor": "Vendor", + "jump_to_the_invoices": "Jump to the invoices.", + "jump_to_the_estimates": "Jump to the estimates.", + "jump_to_the_receipts": "Jump to the receipts.", + "jump_to_the_expenses": "Jump to the expenses.", + "jump_to_the_customers": "Jump to the customers", + "jump_to_the_vendors": "Jump to the vendors.", + "jump_to_the_chart_of_accounts": "Jump to the Chart of Accounts.", + "jump_to_the_bills": "Jump to the bills.", + "jump_to_the_manual_journals": "Jump to the Manual Journals.", + "jump_to_the_items": "Jump to the items.", + "jump_to_the_balance_sheet": "Jump to the Balance Sheet.", + "jump_to_the_profit_loss_sheet": "Jump to the Profit Loss Sheet.", + "jump_to_the_journal_sheet": "Jump to the Journal Sheet.", + "jump_to_the_general_ledger_sheet": "Jump to the General Ledger Sheet.", + "jump_to_the_trial_balance_sheet": "Jump to the Trial Balance Sheet.", + "create_a_new_invoice": "Create a new invoice.", + "create_a_new_estimate": "Create a new estimate.", + "create_a_new_receipt": "Create a new receipt.", + "create_a_new_expense": "Create a new expense.", + "create_a_new_customer": "create_a_new_customer", + "create_a_new_vendor": "Create a new vendor.", + "create_a_new_bill": "Create a new bill.", + "create_a_new_journal": "Create a new journal.", + "create_a_new_item": "Create a new item.", + "close_and_open_sidebar": "Close and open sidebar.", + "and_over": "And Over", + "date_year": "Date/Year", + "date_month": "Date/Month", + "date_week": "Date/Week", + "date_day": "Date/Day", + "date_quarter": "Date/Quarter", + "today": "Today", + "this_week": "This Week", + "this_month": "This Month", + "this_quarter": "This Quarter", + "this_year": "This Year", + "custom_range": "Custom Range", + "total_rows": "Total rows", + "always": "Always", + "none": "None", + "us_dollar": "US Dollar", + "euro": "Euro", + "libyan_diner": "Libyan Diner", + "english": "English", + "arabic": "Arabic", + "just_a_moment_we_re_calculating_your_cost_transactions": "Just a moment! We're calculating your cost transactions and this doesn't take much time.Please check after sometime.", + "refresh": "Refresh", + "total_name": "Total {name}", + "income": "Income", + "total_income": "Total Income", + "cost_of_sales": "Cost of sales", + "total_cost_of_sales": "Total cost of sales", + "gross_profit": "Gross profit", + "total_expenses": "Total Expenses", + "net_operating_income": "Net Operating income", + "other_income": "Other Income", + "total_other_income": "Total other income", + "other_expenses": "Other expenses", + "total_other_expenses": "Total other expenses", + "net_other_income": "Net other income", + "net_income": "Net Income", + "services_that_you_provide_to_customers": " Service : Services that you provide to customers.", + "products_you_buy_and_or_sell": " Inventory : Products you buy and/or sell and that you track quantities of.", + "products_you_buy_and_or_sell_but_don_t_need": " Non-Inventory: Products you buy and/or sell but don’t need to (or can’t) track quantities of, for example, nuts and bolts used in an installation.", + "there_is_no_items_in_the_table_yet": "There is no items in the table yet.", + "congrats_you_are_ready_to_go": "Congrats! You are ready to go", + "go_to_dashboard": "Go to dashboard", + "mr": "Mr.", + "mrs": "Mrs.", + "ms": "Ms.", + "miss": "Miss.", + "dr": "Dr.", + "all_accounts_": "All Accounts", + "search": "Search...", + "Starter": "Starter", + "Essential": "Essential", + "Enterprise": "Enterprise", + "libya": "Libya", + "mm_dd_yy": "MM/DD/YY", + "dd_mm_yy": "DD/MM/YY", + "yy_mm_dd": "YY/MM/DD", + "mm_dd_yy_": "MM-DD-YY", + "dd_mm_yy_": "DD-MM-YY", + "yy_mm_dd_": "YY-MM-DD", + "plan_radio_name":"{name}", + "customers_payments": "Customers Payments", + "receiving_customer_payments_is_one_pleasant_accounting_tasks": "Receiving payments is one of your more pleasant accounting tasks. The payments transactions will appear once receive payments to invoices.", + "estimate_is_used_to_create_bid_proposal_or_quote": "An estimate is used to create a bid, proposal, or quote. The estimate can later be turned into a sales order or an invoice.", + "payment_made_empty_status_description": "The payments transactions will appear once payments made to purchases invoices.", + "bill_empty_status_description": "Record purchases transactions from vendors who make no or partial payment and help you keep track of accounts payable with a vendor.", + "invoices_empty_status_description": "Record sales transactions from customers who make no or partial payment and help you keep track of accounts receivable with a customer.", + "receipt_empty_status_description": "Create a sales receipt any time your customer immediately pays for products or services at the time of sale.", + "there_were_no_purchases_during_the_selected_date_range": "There were no purchases during the selected date range.", + "there_were_no_sales_during_the_selected_date_range": "There were no sales during the selected date range.", + "there_were_no_inventory_transactions_during_the_selected_date_range": "There were no inventory transactions during the selected date range.", + "filter_": "Filter...", + "all_rights_reserved": "© {pre}-{current} All Rights Reserved.", + "congrats_your_account_has_been_created_and_invited": "Congrats! Your account has been created and invited to {organization_name} organization successfully.", + "it_s_time_to_make_your_accounting_really_simple": "It's time to make your accounting really simple!", + "while_we_set_up_your_account_please_remember_to_verify_your_account": "while we set up your account, please remember to verify your account by clicking on the link we sent to your registered email address", + "something_went_wrong": "Something went wrong!", + "please_refresh_the_page": "Please refresh the page", + "waiting_to_redirect": "Waiting to redirect", + "refresh_the_page_if_redirect_not_worked": "Refresh the page if redirect not worked.", + "blog": "Blog", + "support": "Support", + "service_status": "Service Status", + "pricing": "Pricing", + "reseller_partner": "Reseller Partner", + "docs": "Docs", + "Pleasse enter your voucher number that you received from reseller.": "Pleasse enter your voucher number that you received from reseller.", + "Sale and purchase invoices.": "Sale and purchase invoices.", + "Customers/vendors accounts.": "Customers/vendors accounts.", + "Expenses tracking.": "Expenses tracking.", + "Manual journals.": "Manual journals.", + "Financial reports.": "Financial reports.", + "All Capital Starter features.": "All Capital Starter features.", + "Multi-currency.": "Multi-currency.", + "Purchase and sell orders.": "Purchase and sell orders.", + "Inventory management.": "Inventory management.", + "Three users with your accountant.": "Three users with your accountant.", + "Advanced financial reports.": "Advanced financial reports.", + "All Capital Essential features.": "All Capital Essential features.", + "Track multi-branches and locations.": "Track multi-branches and locations.", + "Projects accounting and timesheets.": "Projects accounting and timesheets.", + "Accounting dimensions.": "Accounting dimensions.", + "For one user and accountant.": "For one user and accountant.", + "Monthly": "Monthly", + "Yearly": "Yearly", + "Plans & Payment": "Plans & Payment", + "Initializing": "Initializing", + "Getting started": "Getting started", + "Congratulations": "Congratulations" +} \ No newline at end of file diff --git a/client/src/lang/en/locale.js b/client/src/lang/en/locale.js index 395a78ccc..8ebf81b3e 100644 --- a/client/src/lang/en/locale.js +++ b/client/src/lang/en/locale.js @@ -1,4 +1,5 @@ import printValue from '../printValue'; + export const locale = { mixed: { default: '${path} is invalid', diff --git a/client/src/routes/authentication.js b/client/src/routes/authentication.js index f3fb7da68..224b0db75 100644 --- a/client/src/routes/authentication.js +++ b/client/src/routes/authentication.js @@ -21,12 +21,12 @@ export default [ loader: () => import('containers/Authentication/ResetPassword'), }), }, - { - path: `${BASE_URL}/invite/:token/accept`, - component: LazyLoader({ - loader: () => import('containers/Authentication/InviteAccept'), - }), - }, + // { + // path: `${BASE_URL}/invite/:token/accept`, + // component: LazyLoader({ + // loader: () => import('containers/Authentication/InviteAccept'), + // }), + // }, { path: `${BASE_URL}/register`, component: LazyLoader({ diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 74121459a..02e37c0a2 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -1,37 +1,26 @@ -import { lazy } from 'react'; -import { formatMessage } from 'services/intl'; +import React, { lazy } from 'react'; +import intl from 'react-intl-universal'; // const BASE_URL = '/dashboard'; -export default [ - // Accounts. +export const getDashboardRoutes = () => [ + // // Accounts. { path: `/accounts`, component: lazy(() => import('containers/Accounts/AccountsChart')), - breadcrumb: 'Accounts Chart', + breadcrumb: intl.get('accounts_chart'), hotkey: 'shift+a', - pageTitle: 'Accounts Chart', + pageTitle: intl.get('accounts_chart'), }, - // Custom views. - // { - // path: `/custom_views/:resource_slug/new`, - // component: lazy(() => import('containers/Views/ViewFormPage')), - // breadcrumb: 'New', - // }, - // { - // path: `/custom_views/:view_id/edit`, - // component: lazy(() => import('containers/Views/ViewFormPage')), - // breadcrumb: 'Edit', - // }, // Accounting. { path: `/make-journal-entry`, component: lazy(() => import('containers/Accounting/MakeJournal/MakeJournalEntriesPage'), ), - breadcrumb: 'Make Journal Entry', + breadcrumb: intl.get('make_journal_entry'), hotkey: 'ctrl+shift+m', - pageTitle: formatMessage({ id: 'new_journal' }), + pageTitle: intl.get('new_journal'), sidebarExpand: false, backLink: true, }, @@ -40,8 +29,8 @@ export default [ component: lazy(() => import('containers/Accounting/MakeJournal/MakeJournalEntriesPage'), ), - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_journal' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_journal'), sidebarExpand: false, backLink: true, }, @@ -50,25 +39,25 @@ export default [ component: lazy(() => import('containers/Accounting/JournalsLanding/ManualJournalsList'), ), - breadcrumb: 'Manual Journals', + breadcrumb: intl.get('manual_journals'), hotkey: 'shift+m', - pageTitle: formatMessage({ id: 'manual_journals' }), + pageTitle: intl.get('manual_journals'), }, { path: `/items/categories`, component: lazy(() => import('containers/ItemsCategories/ItemCategoriesList'), ), - breadcrumb: 'Categories', - pageTitle: formatMessage({ id: 'category_list' }), + breadcrumb: intl.get('categories'), + pageTitle: intl.get('category_list'), }, // Items. { path: `/items/:id/edit`, component: lazy(() => import('containers/Items/ItemFormPage')), name: 'item-edit', - breadcrumb: 'Edit Item', - pageTitle: formatMessage({ id: 'edit_item' }), + breadcrumb: intl.get('edit_item'), + pageTitle: intl.get('edit_item'), backLink: true, }, { @@ -76,23 +65,23 @@ export default [ component: lazy({ loader: () => import('containers/Items/ItemFormPage'), }), - breadcrumb: 'Duplicate Item', + breadcrumb: intl.get('duplicate_item'), }, { path: `/items/new`, component: lazy(() => import('containers/Items/ItemFormPage')), name: 'item-new', - breadcrumb: 'New Item', + breadcrumb: intl.get('new_item'), hotkey: 'ctrl+shift+w', - pageTitle: formatMessage({ id: 'new_item' }), + pageTitle: intl.get('new_item'), backLink: true, }, { path: `/items`, component: lazy(() => import('containers/Items/ItemsList')), - breadcrumb: 'Items', + breadcrumb: intl.get('items'), hotkey: 'shift+w', - pageTitle: formatMessage({ id: 'items_list' }), + pageTitle: intl.get('items_list'), }, // Inventory adjustments. @@ -101,8 +90,8 @@ export default [ component: lazy(() => import('containers/InventoryAdjustments/InventoryAdjustmentList'), ), - breadcrumb: 'Inventory a adjustments', - pageTitle: formatMessage({ id: 'inventory_adjustment_list' }), + breadcrumb: intl.get('inventory_adjustments'), + pageTitle: intl.get('inventory_adjustment_list'), }, // Financial Reports. @@ -111,11 +100,10 @@ export default [ component: lazy(() => import('containers/FinancialStatements/GeneralLedger/GeneralLedger'), ), - breadcrumb: 'General Ledger', - hint: - 'Reports every transaction going in and out of your accounts and organized by accounts and date to monitoring activity of accounts.', + breadcrumb: intl.get('general_ledger'), + hint: intl.get('reports_every_transaction_going_in_and_out_of_your'), hotkey: 'shift+4', - pageTitle: formatMessage({ id: 'general_ledger' }), + pageTitle: intl.get('general_ledger'), backLink: true, sidebarExpand: false, }, @@ -124,11 +112,10 @@ export default [ component: lazy(() => import('containers/FinancialStatements/BalanceSheet/BalanceSheet'), ), - breadcrumb: 'Balance Sheet', - hint: - "Reports a company's assets, liabilities and shareholders' equity at a specific point in time with comparison period(s).", + breadcrumb: intl.get('balance_sheet'), + hint: intl.get('reports_a_company_s_assets_liabilities_and_shareholders'), hotkey: 'shift+1', - pageTitle: formatMessage({ id: 'balance_sheet' }), + pageTitle: intl.get('balance_sheet'), backLink: true, sidebarExpand: false, }, @@ -139,11 +126,10 @@ export default [ 'containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet' ), ), - breadcrumb: 'Trial Balance Sheet', - hint: - 'Summarizes the credit and debit balance of each account in your chart of accounts at a specific point in time. ', + breadcrumb: intl.get('trial_balance_sheet'), + hint: intl.get('summarizes_the_credit_and_debit_balance_of_each_account'), hotkey: 'shift+5', - pageTitle: formatMessage({ id: 'trial_balance_sheet' }), + pageTitle: intl.get('trial_balance_sheet'), backLink: true, sidebarExpand: false, }, @@ -152,11 +138,10 @@ export default [ component: lazy(() => import('containers/FinancialStatements/ProfitLossSheet/ProfitLossSheet'), ), - breadcrumb: 'Profit Loss Sheet', - hint: - 'Reports the revenues, costs and expenses incurred during a specific point in time with comparison period(s).', + breadcrumb: intl.get('profit_loss_sheet'), + hint: intl.get('reports_the_revenues_costs_and_expenses'), hotkey: 'shift+2', - pageTitle: formatMessage({ id: 'profit_loss_sheet' }), + pageTitle: intl.get('profit_loss_sheet'), backLink: true, sidebarExpand: false, }, @@ -165,10 +150,9 @@ export default [ component: lazy(() => import('containers/FinancialStatements/ARAgingSummary/ARAgingSummary'), ), - breadcrumb: 'Receivable Aging Summary', - hint: - 'Summarize total unpaid balances of customers invoices with number of days the unpaid invoice is overdue.', - pageTitle: formatMessage({ id: 'receivable_aging_summary' }), + breadcrumb: intl.get('receivable_aging_summary'), + hint: intl.get('summarize_total_unpaid_balances_of_customers_invoices'), + pageTitle: intl.get('receivable_aging_summary'), backLink: true, sidebarExpand: false, }, @@ -177,10 +161,9 @@ export default [ component: lazy(() => import('containers/FinancialStatements/APAgingSummary/APAgingSummary'), ), - breadcrumb: 'Payable Aging Summary', - hint: - 'Summarize total unpaid balances of vendors purchase invoices with the number of days the unpaid invoice is overdue.', - pageTitle: formatMessage({ id: 'payable_aging_summary' }), + breadcrumb: intl.get('payable_aging_summary'), + hint: intl.get('summarize_total_unpaid_balances_of_vendors_purchase'), + pageTitle: intl.get('payable_aging_summary'), backLink: true, sidebarExpand: false, }, @@ -189,11 +172,10 @@ export default [ component: lazy(() => import('containers/FinancialStatements/Journal/Journal'), ), - breadcrumb: 'Journal Sheet', - hint: - 'The debit and credit entries of system transactions, sorted by date.', + breadcrumb: intl.get('journal_sheet'), + hint: intl.get('the_debit_and_credit_entries_of_system_transactions'), hotkey: 'shift+3', - pageTitle: formatMessage({ id: 'journal_sheet' }), + pageTitle: intl.get('journal_sheet'), sidebarExpand: false, backLink: true, }, @@ -204,9 +186,9 @@ export default [ 'containers/FinancialStatements/PurchasesByItems/PurchasesByItems' ), ), - breadcrumb: 'Purchases by Items', + breadcrumb: intl.get('purchases_by_items'), // hotkey: '', - pageTitle: formatMessage({ id: 'purchases_by_items' }), + pageTitle: intl.get('purchases_by_items'), backLink: true, sidebarExpand: false, }, @@ -215,10 +197,11 @@ export default [ component: lazy(() => import('containers/FinancialStatements/SalesByItems/SalesByItems'), ), - breadcrumb: 'Sales by Items', - pageTitle: formatMessage({ id: 'sales_by_items' }), - hint: - 'Summarize the business’s sold items quantity, income and average income rate of each item during a specific point in time.', + breadcrumb: intl.get('sales_by_items'), + pageTitle: intl.get('sales_by_items'), + hint: intl.get( + 'summarize_the_business_s_sold_items_quantity_income_and_average_income_rate', + ), backLink: true, sidebarExpand: false, }, @@ -229,10 +212,9 @@ export default [ 'containers/FinancialStatements/InventoryValuation/InventoryValuation' ), ), - breadcrumb: 'Inventory Valuation ', - hint: - 'Summerize your transactions for each inventory item and how they affect quantity, valuation and weighted average.', - pageTitle: formatMessage({ id: 'inventory_valuation' }), + breadcrumb: intl.get('inventory_valuation'), + hint: intl.get('summerize_your_transactions_for_each_inventory_item'), + pageTitle: intl.get('inventory_valuation'), backLink: true, sidebarExpand: false, }, @@ -243,9 +225,9 @@ export default [ 'containers/FinancialStatements/CustomersBalanceSummary/CustomersBalanceSummary' ), ), - breadcrumb: 'Customers Balance Summary ', - hint: 'Summerize how much each customer owes your business.', - pageTitle: formatMessage({ id: 'customers_balance_summary' }), + breadcrumb: intl.get('customers_balance_summary'), + hint: intl.get('summerize_how_much_each_customer_owes_your_business'), + pageTitle: intl.get('customers_balance_summary'), backLink: true, sidebarExpand: false, }, @@ -256,9 +238,9 @@ export default [ 'containers/FinancialStatements/VendorsBalanceSummary/VendorsBalanceSummary' ), ), - breadcrumb: 'Vendors Balance Summary ', - hint: '..', - pageTitle: formatMessage({ id: 'vendors_balance_summary' }), + breadcrumb: intl.get('vendors_balance_summary'), + hint: intl.get('summerize_the_total_amount_your_business_owes_each_vendor'), + pageTitle: intl.get('vendors_balance_summary'), backLink: true, sidebarExpand: false, }, @@ -269,9 +251,9 @@ export default [ 'containers/FinancialStatements/CustomersTransactions/CustomersTransactions' ), ), - breadcrumb: 'Customers Transactions ', - hint: '..', - pageTitle: formatMessage({ id: 'customers_transactions' }), + breadcrumb: intl.get('customers_transactions'), + hint: intl.get('reports_every_transaction_going_in_and_out_of_each_customer'), + pageTitle: intl.get('customers_transactions'), backLink: true, sidebarExpand: false, }, @@ -282,9 +264,35 @@ export default [ 'containers/FinancialStatements/VendorsTransactions/VendorsTransactions' ), ), - breadcrumb: 'Vendors Transactions ', - hint: '..', - pageTitle: formatMessage({ id: 'vendors_transactions' }), + breadcrumb: intl.get('vendors_transactions'), + hint: intl.get('reports_every_transaction_going_in_and_out_of_each_vendor_supplier'), + pageTitle: intl.get('vendors_transactions'), + backLink: true, + sidebarExpand: false, + }, + { + path: `/financial-reports/cash-flow`, + component: lazy(() => + import( + 'containers/FinancialStatements/CashFlowStatement/CashFlowStatement' + ), + ), + breadcrumb: intl.get('cash_flow_statement'), + hint: intl.get('reports_inflow_and_outflow_of_cash_and_cash_equivalents'), + pageTitle: intl.get('cash_flow_statement'), + backLink: true, + sidebarExpand: false, + }, + { + path: `/financial-reports/inventory-item-details`, + component: lazy(() => + import( + 'containers/FinancialStatements/InventoryItemDetails/InventoryItemDetails' + ), + ), + breadcrumb: intl.get('inventory_item_details'), + hint: intl.get('reports_every_transaction_going_in_and_out_of_your_items'), + pageTitle: intl.get('inventory_item_details'), backLink: true, sidebarExpand: false, }, @@ -293,15 +301,15 @@ export default [ component: lazy(() => import('containers/FinancialStatements/FinancialReports'), ), - breadcrumb: 'Financial Reports', - pageTitle: formatMessage({ id: 'all_financial_reports' }), + breadcrumb: intl.get('financial_reports'), + pageTitle: intl.get('all_financial_reports'), }, // Exchange Rates { path: `/exchange-rates`, component: lazy(() => import('containers/ExchangeRates/ExchangeRatesList')), - breadcrumb: 'Exchange Rates', - pageTitle: formatMessage({ id: 'exchange_rates_list' }), + breadcrumb: intl.get('exchange_rates_list'), + pageTitle: intl.get('exchange_rates_list'), }, // Expenses. { @@ -309,9 +317,9 @@ export default [ component: lazy(() => import('containers/Expenses/ExpenseForm/ExpenseFormPage'), ), - breadcrumb: 'Expenses', + breadcrumb: intl.get('expenses'), hotkey: 'ctrl+shift+x', - pageTitle: formatMessage({ id: 'new_expense' }), + pageTitle: intl.get('new_expense'), sidebarExpand: false, backLink: true, }, @@ -320,8 +328,8 @@ export default [ component: lazy(() => import('containers/Expenses/ExpenseForm/ExpenseFormPage'), ), - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_expense' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_expense'), sidebarExpand: false, backLink: true, }, @@ -330,8 +338,8 @@ export default [ component: lazy(() => import('containers/Expenses/ExpensesLanding/ExpensesList'), ), - breadcrumb: 'Expenses List', - pageTitle: formatMessage({ id: 'expenses_list' }), + breadcrumb: intl.get('expenses_list'), + pageTitle: intl.get('expenses_list'), hotkey: 'shift+x', }, @@ -342,8 +350,8 @@ export default [ import('containers/Customers/CustomerForm/CustomerFormPage'), ), name: 'customer-edit', - breadcrumb: 'Edit Customer', - pageTitle: formatMessage({ id: 'edit_customer' }), + breadcrumb: intl.get('edit_customer'), + pageTitle: intl.get('edit_customer'), backLink: true, }, { @@ -352,9 +360,9 @@ export default [ import('containers/Customers/CustomerForm/CustomerFormPage'), ), name: 'customer-new', - breadcrumb: 'New Customer', + breadcrumb: intl.get('new_customer'), hotkey: 'ctrl+shift+c', - pageTitle: formatMessage({ id: 'new_customer' }), + pageTitle: intl.get('new_customer'), backLink: true, }, { @@ -362,9 +370,9 @@ export default [ component: lazy(() => import('containers/Customers/CustomersLanding/CustomersList'), ), - breadcrumb: 'Customers', + breadcrumb: intl.get('customers'), hotkey: 'shift+c', - pageTitle: formatMessage({ id: 'customers_list' }), + pageTitle: intl.get('customers_list'), }, { path: `/customers/contact_duplicate=/:id`, @@ -372,8 +380,8 @@ export default [ import('containers/Customers/CustomerForm/CustomerFormPage'), ), name: 'duplicate-customer', - breadcrumb: 'Duplicate Customer', - pageTitle: formatMessage({ id: 'new_customer' }), + breadcrumb: intl.get('duplicate_customer'), + pageTitle: intl.get('new_customer'), backLink: true, }, @@ -384,8 +392,8 @@ export default [ import('containers/Vendors/VendorForm/VendorFormPage'), ), name: 'vendor-edit', - breadcrumb: 'Edit Vendor', - pageTitle: formatMessage({ id: 'edit_vendor' }), + breadcrumb: intl.get('edit_vendor'), + pageTitle: intl.get('edit_vendor'), backLink: true, }, { @@ -394,9 +402,9 @@ export default [ import('containers/Vendors/VendorForm/VendorFormPage'), ), name: 'vendor-new', - breadcrumb: 'New Vendor', + breadcrumb: intl.get('new_vendor'), hotkey: 'ctrl+shift+v', - pageTitle: formatMessage({ id: 'new_vendor' }), + pageTitle: intl.get('new_vendor'), backLink: true, }, { @@ -404,9 +412,9 @@ export default [ component: lazy(() => import('containers/Vendors/VendorsLanding/VendorsList'), ), - breadcrumb: 'Vendors', + breadcrumb: intl.get('vendors'), hotkey: 'shift+v', - pageTitle: formatMessage({ id: 'vendors_list' }), + pageTitle: intl.get('vendors_list'), }, { path: `/vendors/contact_duplicate=/:id`, @@ -414,8 +422,8 @@ export default [ import('containers/Vendors/VendorForm/VendorFormPage'), ), name: 'duplicate-vendor', - breadcrumb: 'Duplicate Vendor', - pageTitle: formatMessage({ id: 'new_vendor' }), + breadcrumb: intl.get('duplicate_vendor'), + pageTitle: intl.get('new_vendor'), backLink: true, }, @@ -426,8 +434,8 @@ export default [ import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'), ), name: 'estimate-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_estimate' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_estimate'), backLink: true, sidebarExpand: false, }, @@ -437,8 +445,8 @@ export default [ import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'), ), name: 'convert-to-invoice', - breadcrumb: 'New Estimate', - pageTitle: formatMessage({ id: 'new_estimate' }), + breadcrumb: intl.get('new_estimate'), + pageTitle: intl.get('new_estimate'), backLink: true, sidebarExpand: false, }, @@ -448,9 +456,9 @@ export default [ import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'), ), name: 'estimate-new', - breadcrumb: 'New Estimate', + breadcrumb: intl.get('new_estimate'), hotkey: 'ctrl+shift+e', - pageTitle: formatMessage({ id: 'new_estimate' }), + pageTitle: intl.get('new_estimate'), backLink: true, sidebarExpand: false, }, @@ -460,9 +468,9 @@ export default [ import('containers/Sales/Estimates/EstimatesLanding/EstimatesList'), ), name: 'estimates-list', - breadcrumb: 'Estimates List', + breadcrumb: intl.get('estimates_list'), hotkey: 'shift+e', - pageTitle: formatMessage({ id: 'estimates_list' }), + pageTitle: intl.get('estimates_list'), }, // Invoices. @@ -472,8 +480,8 @@ export default [ import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'), ), name: 'invoice-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_invoice' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_invoice'), sidebarExpand: false, backLink: true, }, @@ -483,9 +491,9 @@ export default [ import('containers/Sales/Invoices/InvoiceForm/InvoiceFormPage'), ), name: 'invoice-new', - breadcrumb: 'New Invoice', + breadcrumb: intl.get('new_invoice'), hotkey: 'ctrl+shift+i', - pageTitle: formatMessage({ id: 'new_invoice' }), + pageTitle: intl.get('new_invoice'), sidebarExpand: false, backLink: true, }, @@ -494,9 +502,9 @@ export default [ component: lazy(() => import('containers/Sales/Invoices/InvoicesLanding/InvoicesList'), ), - breadcrumb: 'Invoices List', + breadcrumb: intl.get('invoices_list'), hotkey: 'shift+i', - pageTitle: formatMessage({ id: 'invoices_list' }), + pageTitle: intl.get('invoices_list'), }, // Sales Receipts. @@ -506,8 +514,8 @@ export default [ import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'), ), name: 'receipt-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_receipt' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_receipt'), backLink: true, sidebarExpand: false, }, @@ -517,9 +525,9 @@ export default [ import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'), ), name: 'receipt-new', - breadcrumb: 'New Receipt', + breadcrumb: intl.get('new_receipt'), hotkey: 'ctrl+shift+r', - pageTitle: formatMessage({ id: 'new_receipt' }), + pageTitle: intl.get('new_receipt'), backLink: true, sidebarExpand: false, }, @@ -528,9 +536,9 @@ export default [ component: lazy(() => import('containers/Sales/Receipts/ReceiptsLanding/ReceiptsList'), ), - breadcrumb: 'Receipts List', + breadcrumb: intl.get('receipts_list'), hotkey: 'shift+r', - pageTitle: formatMessage({ id: 'receipts_list' }), + pageTitle: intl.get('receipts_list'), }, // Payment receives @@ -542,8 +550,8 @@ export default [ ), ), name: 'payment-receive-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_payment_receive' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_payment_receive'), backLink: true, sidebarExpand: false, }, @@ -555,8 +563,8 @@ export default [ ), ), name: 'payment-receive-new', - breadcrumb: 'New Payment Receive', - pageTitle: formatMessage({ id: 'new_payment_receive' }), + breadcrumb: intl.get('new_payment_receive'), + pageTitle: intl.get('new_payment_receive'), backLink: true, sidebarExpand: false, }, @@ -567,8 +575,8 @@ export default [ 'containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList' ), ), - breadcrumb: 'Payment Receives List', - pageTitle: formatMessage({ id: 'payment_receives_list' }), + breadcrumb: intl.get('payment_receives_list'), + pageTitle: intl.get('payment_receives_list'), }, // Bills @@ -578,8 +586,8 @@ export default [ import('containers/Purchases/Bills/BillForm/BillFormPage'), ), name: 'bill-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_bill' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_bill'), sidebarExpand: false, backLink: true, }, @@ -589,9 +597,9 @@ export default [ import('containers/Purchases/Bills/BillForm/BillFormPage'), ), name: 'bill-new', - breadcrumb: 'New Bill', + breadcrumb: intl.get('new_bill'), hotkey: 'ctrl+shift+b', - pageTitle: formatMessage({ id: 'new_bill' }), + pageTitle: intl.get('new_bill'), sidebarExpand: false, backLink: true, }, @@ -600,16 +608,16 @@ export default [ component: lazy(() => import('containers/Purchases/Bills/BillsLanding/BillsList'), ), - breadcrumb: 'Bills List', + breadcrumb: intl.get('bills_list'), hotkey: 'shift+b', - pageTitle: formatMessage({ id: 'bills_list' }), + pageTitle: intl.get('bills_list'), }, // Subscription billing. { path: `/billing`, component: lazy(() => import('containers/Subscriptions/BillingForm')), - breadcrumb: 'New Billing', + breadcrumb: intl.get('new_billing'), }, // Payment modes. { @@ -620,8 +628,8 @@ export default [ ), ), name: 'payment-made-edit', - breadcrumb: 'Edit', - pageTitle: formatMessage({ id: 'edit_payment_made' }), + breadcrumb: intl.get('edit'), + pageTitle: intl.get('edit_payment_made'), sidebarExpand: false, backLink: true, }, @@ -633,8 +641,8 @@ export default [ ), ), name: 'payment-made-new', - breadcrumb: 'New Payment Made', - pageTitle: formatMessage({ id: 'new_payment_made' }), + breadcrumb: intl.get('new_payment_made'), + pageTitle: intl.get('new_payment_made'), sidebarExpand: false, backLink: true, }, @@ -645,13 +653,13 @@ export default [ 'containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeList' ), ), - breadcrumb: 'Payment Made List', - pageTitle: formatMessage({ id: 'payment_made_list' }), + breadcrumb: intl.get('payment_made_list'), + pageTitle: intl.get('payment_made_list'), }, // Homepage { path: `/`, component: lazy(() => import('containers/Homepage/Homepage')), - breadcrumb: 'Home', + breadcrumb: intl.get('homepage'), }, ]; diff --git a/client/src/routes/preferences.js b/client/src/routes/preferences.js index 7bd0cb385..b83aad542 100644 --- a/client/src/routes/preferences.js +++ b/client/src/routes/preferences.js @@ -1,7 +1,7 @@ import General from 'containers/Preferences/General/General'; import Users from 'containers/Preferences/Users/Users'; import Accountant from 'containers/Preferences/Accountant/Accountant'; -import Accounts from 'containers/Preferences/Accounts/Accounts'; +// import Accounts from 'containers/Preferences/Accounts/Accounts'; import Currencies from 'containers/Preferences/Currencies/Currencies'; import Item from 'containers/Preferences/Item/Item'; import DefaultRoute from '../containers/Preferences/DefaultRoute'; diff --git a/client/src/routes/register.js b/client/src/routes/register.js index 44277cd21..2d103816d 100644 --- a/client/src/routes/register.js +++ b/client/src/routes/register.js @@ -1,7 +1,6 @@ import LazyLoader from 'components/LazyLoader'; export default [ - { path: '/register/subscription', component: LazyLoader({ diff --git a/client/src/services/axios.js b/client/src/services/axios.js index 5c8761bfb..fdacb692a 100644 --- a/client/src/services/axios.js +++ b/client/src/services/axios.js @@ -17,6 +17,8 @@ http.interceptors.request.use((request) => { if (locale) { request.headers.common['Accept-Language'] = locale; } + request.headers.common['Accept-Language'] = 'ar'; + return request; }, (error) => { return Promise.reject(error); diff --git a/client/src/services/intl.js b/client/src/services/intl.js deleted file mode 100644 index f2d61bbee..000000000 --- a/client/src/services/intl.js +++ /dev/null @@ -1,24 +0,0 @@ -import { createIntl, createIntlCache } from 'react-intl'; -import messages from 'lang/en'; -import { setLocale } from 'yup'; -import {locale} from 'lang/en/locale'; - - -// This is optional but highly recommended -// since it prevents memory leak -const cache = createIntlCache() - -const intl = createIntl({ - locale: 'en', - messages, -}, cache); - -setLocale(locale) - -const { formatMessage } = intl; - -export { - formatMessage, -}; - -export default intl; diff --git a/client/src/store/financialStatement/financialStatements.actions.js b/client/src/store/financialStatement/financialStatements.actions.js index a972d42db..0ef8f9a41 100644 --- a/client/src/store/financialStatement/financialStatements.actions.js +++ b/client/src/store/financialStatement/financialStatements.actions.js @@ -178,3 +178,29 @@ export function toggleVendorsTransactionsFilterDrawer(toggle) { }, }; } + +/** + * Toggle display of the cash flow statement filter drawer. + * @param {boolean} toggle + */ +export function toggleCashFlowStatementFilterDrawer(toggle) { + return { + type: `${t.CASH_FLOW_STATEMENT}/${t.DISPLAY_FILTER_DRAWER_TOGGLE}`, + payload: { + toggle, + }, + }; +} + +/** + * Toggles display of the inventory item details filter drawer. + * @param {boolean} toggle + */ + export function toggleInventoryItemDetailsFilterDrawer(toggle) { + return { + type: `${t.INVENTORY_ITEM_DETAILS}/${t.DISPLAY_FILTER_DRAWER_TOGGLE}`, + payload: { + toggle, + }, + }; +} diff --git a/client/src/store/financialStatement/financialStatements.reducer.js b/client/src/store/financialStatement/financialStatements.reducer.js index e0b725b33..63fea46b2 100644 --- a/client/src/store/financialStatement/financialStatements.reducer.js +++ b/client/src/store/financialStatement/financialStatements.reducer.js @@ -45,6 +45,12 @@ const initialState = { vendorsTransactions: { displayFilterDrawer: false, }, + cashFlowStatement: { + displayFilterDrawer: false, + }, + inventoryItemDetails: { + displayFilterDrawer: false, + }, }; /** @@ -91,4 +97,9 @@ export default createReducer(initialState, { t.VENDORS_TRANSACTIONS, 'vendorsTransactions', ), + ...financialStatementFilterToggle(t.CASH_FLOW_STATEMENT, 'cashFlowStatement'), + ...financialStatementFilterToggle( + t.INVENTORY_ITEM_DETAILS, + 'inventoryItemDetails', + ), }); diff --git a/client/src/store/financialStatement/financialStatements.selectors.js b/client/src/store/financialStatement/financialStatements.selectors.js index c87c10cc9..f19337fa2 100644 --- a/client/src/store/financialStatement/financialStatements.selectors.js +++ b/client/src/store/financialStatement/financialStatements.selectors.js @@ -65,6 +65,14 @@ export const vendorsTransactionsFilterDrawerSelector = (state) => { return filterDrawerByTypeSelector('vendorsTransactions')(state); }; +export const cashFlowStatementFilterDrawerSelector = (state) => { + return filterDrawerByTypeSelector('cashFlowStatement')(state); +}; + +export const inventoryItemDetailsDrawerFilter = (state) => { + return filterDrawerByTypeSelector('inventoryItemDetails')(state); +}; + /** * Retrieve balance sheet filter drawer. */ @@ -211,3 +219,23 @@ export const getVendorsTransactionsFilterDrawer = createSelector( return isOpen; }, ); + +/** + * Retrieve cash flow statement filter drawer. + */ +export const getCashFlowStatementFilterDrawer = createSelector( + cashFlowStatementFilterDrawerSelector, + (isOpen) => { + return isOpen; + }, +); + +/** + * Retrieve inventory item details filter drawer. + */ +export const getInventoryItemDetailsFilterDrawer = createSelector( + inventoryItemDetailsDrawerFilter, + (isOpen) => { + return isOpen; + }, +); diff --git a/client/src/store/financialStatement/financialStatements.types.js b/client/src/store/financialStatement/financialStatements.types.js index 7b49a6739..ef82d5b92 100644 --- a/client/src/store/financialStatement/financialStatements.types.js +++ b/client/src/store/financialStatement/financialStatements.types.js @@ -14,4 +14,6 @@ export default { VENDORS_BALANCE_SUMMARY: 'VENDORS BALANCE SUMMARY', CUSTOMERS_TRANSACTIONS: 'CUSTOMERS TRANSACTIONS', VENDORS_TRANSACTIONS: 'VENDORS TRANSACTIONS', + CASH_FLOW_STATEMENT: 'CASH FLOW STATEMENT', + INVENTORY_ITEM_DETAILS: 'INVENTORY ITEM DETAILS', }; diff --git a/client/src/store/plans/plans.actions.js b/client/src/store/plans/plans.actions.js new file mode 100644 index 000000000..7e7c3d2cf --- /dev/null +++ b/client/src/store/plans/plans.actions.js @@ -0,0 +1,5 @@ +import t from 'store/types'; + +export const initSubscriptionPlans = () => ({ + type: t.INIT_SUBSCRIPTION_PLANS +}); \ No newline at end of file diff --git a/client/src/store/plans/plans.reducer.js b/client/src/store/plans/plans.reducer.js index 5f661231a..e0d9bbf3b 100644 --- a/client/src/store/plans/plans.reducer.js +++ b/client/src/store/plans/plans.reducer.js @@ -1,52 +1,84 @@ import { createReducer } from '@reduxjs/toolkit'; +import intl from 'react-intl-universal'; +import t from 'store/types'; + +const getSubscriptionPlans = () => [ + { + name: intl.get('Starter'), + slug: 'starter', + description: [ + intl.get('Sale and purchase invoices.'), + intl.get('Customers/vendors accounts.'), + intl.get('Expenses tracking'), + intl.get('Manual journals.'), + intl.get('Financial reports.'), + intl.get('For one user and accountant.'), + ], + price: { + month: '100', + year: '1,200', + }, + currencyCode: 'LYD', + }, + { + name: intl.get('Essential'), + slug: 'plus', + description: [ + intl.get('All Capital Starter features.'), + intl.get('Multi-currency.'), + intl.get('Purchase and sell orders.'), + intl.get('Inventory management.'), + intl.get('Three users with your accountant.'), + intl.get('Advanced financial reports.'), + ], + price: { + month: '200', + year: '2,400', + }, + currencyCode: 'LYD', + }, + { + name: intl.get('Enterprise'), + slug: 'enterprise', + description: [ + intl.get('All Capital Essential features.'), + intl.get('Track multi-branches and locations.'), + intl.get('Projects accounting and timesheets.'), + intl.get('Accounting dimensions.'), + ], + price: { + month: '300', + year: '3,400', + }, + currencyCode: 'LYD', + }, +]; + +const getSubscriptionPeriods = () => [ + { + slug: 'month', + label: intl.get('Monthly'), + }, + { + slug: 'year', + label: intl.get('Yearly'), + }, +]; const initialState = { - plans: [ - { - name: 'Free', - slug: 'free', - description: [ - 'Sales/purchases module.', - 'Expense module.', - 'Inventory module.', - 'Unlimited status pages.', - 'Unlimited status pages.', - ], - price: { - month: '100', - year: '1200', - }, - currencyCode: 'LYD', - }, - { - name: 'Pro', - slug: 'pro', - description: [ - 'Sales/purchases module.', - 'Expense module.', - 'Inventory module.', - 'Unlimited status pages.', - 'Unlimited status pages.', - ], - price: { - month: '200', - year: '2400', - }, - currencyCode: 'LYD', - }, - ], - periods: [ - { - slug: 'month', - label: 'Monthly', - }, - { - slug: 'year', - label: 'Yearly', - }, - ], + plans: [], + periods: [], }; export default createReducer(initialState, { + /** + * Initialize the subscription plans. + */ + [t.INIT_SUBSCRIPTION_PLANS]: (state) => { + const plans = getSubscriptionPlans(); + const periods = getSubscriptionPeriods(); + state.plans = plans; + state.periods = periods; + }, }); diff --git a/client/src/store/plans/plans.types.js b/client/src/store/plans/plans.types.js new file mode 100644 index 000000000..fe29fa7fd --- /dev/null +++ b/client/src/store/plans/plans.types.js @@ -0,0 +1,4 @@ + +export default { + INIT_SUBSCRIPTION_PLANS: 'INIT_SUBSCRIPTION_PLANS', +}; \ No newline at end of file diff --git a/client/src/store/types.js b/client/src/store/types.js index d72aa65e6..b2d4d0ef8 100644 --- a/client/src/store/types.js +++ b/client/src/store/types.js @@ -27,6 +27,7 @@ import paymentMades from './PaymentMades/paymentMades.type'; import organizations from './organizations/organizations.types'; import subscription from './subscription/subscription.types'; import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.type'; +import plans from './plans/plans.types'; export default { ...authentication, @@ -57,5 +58,6 @@ export default { ...paymentMades, ...organizations, ...subscription, - ...inventoryAdjustments + ...inventoryAdjustments, + ...plans }; diff --git a/client/src/style/App.scss b/client/src/style/App.scss index 18d88ff4f..9426135d6 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -171,7 +171,7 @@ body.hide-scrollbar .Pane2 { width: 1em; fill: currentColor; } - +.ReactVirtualized__Grid, .ReactVirtualized__List { direction: inherit !important; } /* List default theme */ .ReactVirtualized__List { @@ -180,4 +180,17 @@ body.hide-scrollbar .Pane2 { .bp3-drawer{ box-shadow: 0 0 0; +} + +// RTL Icons. +html[dir="rtl"] { + .bp3-icon-caret-right{ + transform: scaleX(-1); + } +} + +html[lang^="ar"] { + body{ + letter-spacing: -0.01rem; + } } \ No newline at end of file diff --git a/client/src/style/components/DataTable/DataTableEmptyStatus.scss b/client/src/style/components/DataTable/DataTableEmptyStatus.scss index 97fbe7720..c2c3ddbeb 100644 --- a/client/src/style/components/DataTable/DataTableEmptyStatus.scss +++ b/client/src/style/components/DataTable/DataTableEmptyStatus.scss @@ -14,12 +14,20 @@ margin-right: auto; margin-top: 0; line-height: 1.4; + + html[lang='ar'] & { + font-size: 22px; + } } &__desc { font-size: 16px; color: #1f3255; opacity: 0.8; line-height: 1.6; + + html[lang='ar'] & { + font-size: 18px; + } } &__actions { margin-top: 26px; diff --git a/client/src/style/containers/Dashboard/Sidebar.scss b/client/src/style/containers/Dashboard/Sidebar.scss index a6efca38d..66ba237a3 100644 --- a/client/src/style/containers/Dashboard/Sidebar.scss +++ b/client/src/style/containers/Dashboard/Sidebar.scss @@ -168,6 +168,11 @@ text-transform: uppercase; font-weight: 500; letter-spacing: 1px; + + html[lang^="ar"] &{ + font-size: 12px; + letter-spacing: 0; + } } &:hover .bp3-button.menu-item__add-btn { diff --git a/client/src/style/fonts/NotoSansArabicUI-SemiCondensed.woff b/client/src/style/fonts/NotoSansArabicUI-SemiCondensed.woff new file mode 100644 index 000000000..f5ca1cafd Binary files /dev/null and b/client/src/style/fonts/NotoSansArabicUI-SemiCondensed.woff differ diff --git a/client/src/style/fonts/NotoSansArabicUI-SemiCondensedBlack.woff b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedBlack.woff new file mode 100644 index 000000000..f0b741a49 Binary files /dev/null and b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedBlack.woff differ diff --git a/client/src/style/fonts/NotoSansArabicUI-SemiCondensedMedium.woff b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedMedium.woff new file mode 100644 index 000000000..ca9612a75 Binary files /dev/null and b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedMedium.woff differ diff --git a/client/src/style/fonts/NotoSansArabicUI-SemiCondensedSemiBold.woff b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedSemiBold.woff new file mode 100644 index 000000000..c9270c494 Binary files /dev/null and b/client/src/style/fonts/NotoSansArabicUI-SemiCondensedSemiBold.woff differ diff --git a/client/src/style/fonts/SegoeArabicUI-Bold.woff2 b/client/src/style/fonts/SegoeArabicUI-Bold.woff2 new file mode 100644 index 000000000..8ab721e9e Binary files /dev/null and b/client/src/style/fonts/SegoeArabicUI-Bold.woff2 differ diff --git a/client/src/style/fonts/SegoeArabicUI-Light.woff2 b/client/src/style/fonts/SegoeArabicUI-Light.woff2 new file mode 100644 index 000000000..904ad681e Binary files /dev/null and b/client/src/style/fonts/SegoeArabicUI-Light.woff2 differ diff --git a/client/src/style/fonts/SegoeArabicUI-Regular.woff2 b/client/src/style/fonts/SegoeArabicUI-Regular.woff2 new file mode 100644 index 000000000..ee67765e3 Binary files /dev/null and b/client/src/style/fonts/SegoeArabicUI-Regular.woff2 differ diff --git a/client/src/style/fonts/SegoeArabicUI-SemiBold.woff2 b/client/src/style/fonts/SegoeArabicUI-SemiBold.woff2 new file mode 100644 index 000000000..4737f8f6e Binary files /dev/null and b/client/src/style/fonts/SegoeArabicUI-SemiBold.woff2 differ diff --git a/client/src/style/objects/typography.scss b/client/src/style/objects/typography.scss index ecf59becd..28c25d375 100644 --- a/client/src/style/objects/typography.scss +++ b/client/src/style/objects/typography.scss @@ -2,6 +2,10 @@ body{ color: #1f3255; + + [lang='ar'] &{ + font-size: 15px; + } } .#{$ns}-heading{ diff --git a/client/src/style/pages/Dashboard/Dashboard.scss b/client/src/style/pages/Dashboard/Dashboard.scss index f59a2942d..338d8a07e 100644 --- a/client/src/style/pages/Dashboard/Dashboard.scss +++ b/client/src/style/pages/Dashboard/Dashboard.scss @@ -124,6 +124,8 @@ $dashboard-views-bar-height: 45px; a { margin: auto 0; + /*!rtl:ignore*/ + direction: ltr; } } @@ -460,6 +462,9 @@ $dashboard-views-bar-height: 45px; .button--new-view { margin: 0; height: $dashboard-views-bar-height; + min-width: 35px; + padding-left: 5px; + padding-right: 5px; &, &:hover, diff --git a/client/src/style/pages/FinancialStatements/CashFlowStatement.scss b/client/src/style/pages/FinancialStatements/CashFlowStatement.scss new file mode 100644 index 000000000..2ff22b8f2 --- /dev/null +++ b/client/src/style/pages/FinancialStatements/CashFlowStatement.scss @@ -0,0 +1,50 @@ +.financial-sheet { + &--cash-flow-statement { + .financial-sheet__table { + .thead, + .tbody { + .tr .td.account_name ~ .td, + .tr .th.account_name ~ .th { + text-align: right; + } + } + .tbody { + .tr:not(.no-results) { + &.row-type--CASH_END_PERIOD{ + border-bottom: 3px double #333; + } + .td { + border-bottom: 0; + padding-top: 0.4rem; + padding-bottom: 0.4rem; + } + + &.row-type--TOTAL { + font-weight: 500; + + &:not(:first-child) .td { + border-top: 1px solid #bbb; + } + } + } + + .tr.is-expanded { + .td.total, + .td.date-period{ + .cell-inner { + display: none; + } + } + } + } + } + } +} + +.financial-statement--cash-flow { + .financial-header-drawer { + .bp3-drawer { + max-height: 450px; + } + } +} diff --git a/client/src/style/pages/FinancialStatements/InventoryItemDetails.scss b/client/src/style/pages/FinancialStatements/InventoryItemDetails.scss new file mode 100644 index 000000000..d2458d1c3 --- /dev/null +++ b/client/src/style/pages/FinancialStatements/InventoryItemDetails.scss @@ -0,0 +1,77 @@ +.financial-sheet { + &--inventory-item-details { + width: 100%; + + .financial-sheet__table { + .tbody, + .thead { + .tr .td.transaction_id ~ .td, + .tr .th.transaction_id ~ .th { + text-align: right; + } + } + .tbody { + .tr .td { + padding-top: 0.2rem; + padding-bottom: 0.2rem; + border-top-color: transparent; + border-bottom-color: transparent; + &.date { + > div { + display: flex; + } + + span.force-width { + position: relative; + } + } + } + .tr:not(.no-results) .td { + border-left: 1px solid #ececec; + } + + .tr.row-type { + &--ITEM { + .td { + &.transaction_type { + border-left-color: transparent; + } + } + &:not(:first-child).is-expanded .td { + border-top: 1px solid #ddd; + } + } + &--ITEM, + &--OPENING_ENTRY, + &--CLOSING_ENTRY { + font-weight: 500; + } + &--ITEM { + &.is-expanded { + .td.value .cell-inner { + display: none; + } + } + } + } + } + } + } +} +.number-format-dropdown { + .toggles-fields { + .bp3-form-group:first-child { + display: none; + } + } + .form-group--money-format { + display: none; + } +} +.financial-statement--inventory-details { + .financial-header-drawer { + .bp3-drawer { + max-height: 350px; + } + } +} diff --git a/client/src/style/pages/Setup/SetupPage.scss b/client/src/style/pages/Setup/SetupPage.scss index 7393911ed..1a1b3531d 100644 --- a/client/src/style/pages/Setup/SetupPage.scss +++ b/client/src/style/pages/Setup/SetupPage.scss @@ -1,5 +1,5 @@ .setup-page { - max-width: 1400px; + max-width: 1600px; &__right-section { display: flex; @@ -26,7 +26,7 @@ } &__content { - width: 70%; + width: 100%; padding-bottom: 40px; } @@ -127,7 +127,6 @@ .setup-page-steps { &-container { - width: 80%; margin: 0 auto; padding: 50px 0 0; } @@ -167,13 +166,15 @@ display: none; } &.is-active { + text-decoration: underline; + &::before { background-color: #75859c; } ~ li { &:before, &:after { - background: #ebebeb; + background: #e0e0e0; } } p.wizard-info { diff --git a/client/src/style/pages/Setup/Subscription.scss b/client/src/style/pages/Setup/Subscription.scss index fa07c25c9..3a8f0b5af 100644 --- a/client/src/style/pages/Setup/Subscription.scss +++ b/client/src/style/pages/Setup/Subscription.scss @@ -1,7 +1,6 @@ .setup-subscription-form{ - max-width: 800px; margin: 0 auto; - padding: 0 60px; + padding: 0 80px; margin-top: 40px; } diff --git a/client/src/style/pages/Subscription/BillingPlans.scss b/client/src/style/pages/Subscription/BillingPlans.scss index aaa828753..cc9948b53 100644 --- a/client/src/style/pages/Subscription/BillingPlans.scss +++ b/client/src/style/pages/Subscription/BillingPlans.scss @@ -1,12 +1,16 @@ .billing-plans{ + + .paragraph{ + font-size: 15px; + } &__section{ - margin-bottom: 35px; + margin-bottom: 40px; .title{ - font-size: 22px; - font-weight: 500; + font-size: 20px; + font-weight: 600; color: #6b7382; margin-top: 0; margin-bottom: 12px; diff --git a/client/src/style/pages/Subscription/PlanRadio.scss b/client/src/style/pages/Subscription/PlanRadio.scss index ba8c85d45..bd0c714f3 100644 --- a/client/src/style/pages/Subscription/PlanRadio.scss +++ b/client/src/style/pages/Subscription/PlanRadio.scss @@ -9,7 +9,7 @@ display: flex; flex-direction: column; width: 215px; - height: 267px; + min-height: 277px; border-radius: 5px; padding: 15px; border: 1px solid #dcdcdc; @@ -32,10 +32,10 @@ &__name { background: #3657ff; border-radius: 3px; - padding: 2px 8px 2px 8px; + padding: 2px 10px; font-size: 13px; color: #fff; - margin-bottom: 16px; + margin-bottom: 18px; height: 21px; text-transform: uppercase; } @@ -47,13 +47,23 @@ list-style: none; li{ + position: relative; + padding-left: 12px; margin-bottom: 9px; + + &:before{ + content: '-'; + position: absolute; + left: 0; + opacity: 0.6; + } } } } &__price { margin-top: auto; font-size: 15px; + padding-top: 10px; } &__amount { font-weight: 600; diff --git a/client/src/style/pages/fonts.scss b/client/src/style/pages/fonts.scss index 88da31425..76e0d759f 100644 --- a/client/src/style/pages/fonts.scss +++ b/client/src/style/pages/fonts.scss @@ -29,3 +29,73 @@ font-weight: 900; font-display: swap; } + +// arabic regular +@font-face { + font-family: Noto Sans Arabic; + src: local('Noto Sans'), + url('../fonts/NotoSansArabicUI-SemiCondensed.woff') format('woff'); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +// arabic black +@font-face { + font-family: Noto Sans Arabic; + src: local('Noto Sans'), + url('../fonts/NotoSansArabicUI-SemiCondensedBlack.woff') format('woff'); + font-style: normal; + font-weight: 900; + font-display: swap; +} + +//arabic Medium +@font-face { + font-family: Noto Sans Arabic; + src: local('Noto Sans'), + url('../fonts/NotoSansArabicUI-SemiCondensedMedium.woff') format('woff'); + font-style: normal; + font-weight: 500; + font-display: swap; +} + +//arabic SemiBold +@font-face { + font-family: Noto Sans Arabic; + src: local('Noto Sans'), + url('../fonts/NotoSansArabicUI-SemiCondensedSemiBold.woff') format('woff'); + font-style: normal; + font-weight: 600; + font-display: swap; +} + +// Segoe UI Arabic - Regular +@font-face { + font-family: 'Segoe UI'; + src: local('Noto Sans'), + url('../fonts/SegoeArabicUI-Regular.woff2') format('woff'); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +// Segoe UI Arabic - Bold +@font-face { + font-family: 'Segoe UI'; + src: local('Noto Sans'), + url('../fonts/SegoeArabicUI-Bold.woff2') format('woff'); + font-style: normal; + font-weight: 900; + font-display: swap; +} + +// Segoe UI Arabic - Semi Bold +@font-face { + font-family: 'Segoe UI'; + src: local('Noto Sans'), + url('../fonts/SegoeArabicUI-SemiBold.woff2') format('woff'); + font-style: normal; + font-weight: 500; + font-display: swap; +} diff --git a/client/src/style/variables.scss b/client/src/style/variables.scss index a60557f6b..63c0400ee 100644 --- a/client/src/style/variables.scss +++ b/client/src/style/variables.scss @@ -16,8 +16,9 @@ $menu-item-color-active: $light-gray3; $breadcrumbs-collapsed-icon: url("data:image/svg+xml,"); $sidebar-zindex: 15; -$pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, - Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif; +$pt-font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, + Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, + Icons16, sans-serif; $button-box-shadow: 0 0 0 !default; $button-box-shadow-active: 0 0 0 !default; diff --git a/server/.env.example b/server/.env.example index 31287ed5d..cc54ba76b 100644 --- a/server/.env.example +++ b/server/.env.example @@ -26,7 +26,6 @@ DB_MANAGER_SUPER_PASSWORD=root MONGODB_DATABASE_URL=mongodb://localhost/bigcapital -EASY_SMS_TOKEN=b0JDZW56RnV6aEthb0RGPXVEcUI JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI diff --git a/server/package.json b/server/package.json index b7514a98b..3720acbb5 100644 --- a/server/package.json +++ b/server/package.json @@ -19,6 +19,7 @@ "dependencies": { "@hapi/boom": "^7.4.3", "@types/i18n": "^0.8.7", + "@types/mathjs": "^6.0.12", "accepts": "^1.3.7", "accounting": "^0.4.1", "agenda": "^3.1.0", @@ -34,6 +35,7 @@ "crypto-random-string": "^3.2.0", "csurf": "^1.10.0", "deep-map": "^2.0.0", + "deepdash": "^5.3.7", "dotenv": "^8.1.0", "errorhandler": "^1.5.1", "es6-weak-map": "^2.0.3", @@ -55,6 +57,7 @@ "knex-db-manager": "^0.6.1", "libphonenumber-js": "^1.9.6", "lodash": "^4.17.15", + "mathjs": "^9.4.0", "memory-cache": "^0.2.0", "moment": "^2.24.0", "moment-range": "^4.0.2", diff --git a/server/src/api/controllers/AccountTypes.ts b/server/src/api/controllers/AccountTypes.ts index 3c5bf3d35..792a8ba6a 100644 --- a/server/src/api/controllers/AccountTypes.ts +++ b/server/src/api/controllers/AccountTypes.ts @@ -27,8 +27,10 @@ export default class AccountsTypesController extends BaseController { * @return {Response} */ getAccountTypesList(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + try { - const accountTypes = this.accountsTypesService.getAccountsTypes(); + const accountTypes = this.accountsTypesService.getAccountsTypes(tenantId); return res.status(200).send({ account_types: this.transfromToResponse(accountTypes, ['label'], req), diff --git a/server/src/api/controllers/Accounts.ts b/server/src/api/controllers/Accounts.ts index b4e378434..e48f1f9e3 100644 --- a/server/src/api/controllers/Accounts.ts +++ b/server/src/api/controllers/Accounts.ts @@ -8,6 +8,7 @@ import { IAccountDTO, IAccountsFilter } from 'interfaces'; import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { DATATYPES_LENGTH } from 'data/DataTypes'; +import I18nService from 'services/I18n/I18nService'; @Service() export default class AccountsController extends BaseController { @@ -17,6 +18,9 @@ export default class AccountsController extends BaseController { @Inject() dynamicListService: DynamicListingService; + @Inject() + i18nService: I18nService; + /** * Router constructor method. */ diff --git a/server/src/api/controllers/BaseController.ts b/server/src/api/controllers/BaseController.ts index 5627d57cb..403504c52 100644 --- a/server/src/api/controllers/BaseController.ts +++ b/server/src/api/controllers/BaseController.ts @@ -10,7 +10,7 @@ export default class BaseController { * Converts plain object keys to cameCase style. * @param {Object} data */ - private dataToCamelCase(data) { + protected dataToCamelCase(data) { return mapKeysDeep(data, (v, k) => camelCase(k)); } @@ -19,7 +19,7 @@ export default class BaseController { * @param {Request} req * @param options */ - matchedBodyData(req: Request, options: any = {}) { + protected matchedBodyData(req: Request, options: any = {}) { const data = matchedData(req, { locations: ['body'], includeOptionals: true, @@ -32,7 +32,7 @@ export default class BaseController { * Matches the query data from validation schema. * @param {Request} req */ - matchedQueryData(req: Request) { + protected matchedQueryData(req: Request) { const data = matchedData(req, { locations: ['query'], }); @@ -45,7 +45,7 @@ export default class BaseController { * @param {Response} res * @param {NextFunction} next */ - validationResult(req: Request, res: Response, next: NextFunction) { + protected validationResult(req: Request, res: Response, next: NextFunction) { const validationErrors = validationResult(req); if (!validationErrors.isEmpty()) { @@ -61,7 +61,7 @@ export default class BaseController { * Transform the given data to response. * @param {any} data */ - transfromToResponse( + protected transfromToResponse( data: any, translatable?: string | string[], req?: Request @@ -85,16 +85,16 @@ export default class BaseController { * Async middleware. * @param {function} callback */ - asyncMiddleware(callback) { + protected asyncMiddleware(callback) { return asyncMiddleware(callback); } /** - * - * @param {Request} req - * @returns + * + * @param {Request} req + * @returns */ - accepts(req) { + protected accepts(req) { return accepts(req); } } diff --git a/server/src/api/controllers/FinancialStatements.ts b/server/src/api/controllers/FinancialStatements.ts index 497b499cc..dbda169f6 100644 --- a/server/src/api/controllers/FinancialStatements.ts +++ b/server/src/api/controllers/FinancialStatements.ts @@ -15,6 +15,8 @@ import CustomerBalanceSummaryController from './FinancialStatements/CustomerBala import VendorBalanceSummaryController from './FinancialStatements/VendorBalanceSummary'; import TransactionsByCustomers from './FinancialStatements/TransactionsByCustomers'; import TransactionsByVendors from './FinancialStatements/TransactionsByVendors'; +import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow'; +import InventoryDetailsController from './FinancialStatements/InventoryDetails'; @Service() export default class FinancialStatementsService { @@ -77,6 +79,14 @@ export default class FinancialStatementsService { '/transactions-by-vendors', Container.get(TransactionsByVendors).router(), ); + router.use( + '/cash-flow', + Container.get(CashFlowStatementController).router(), + ); + router.use( + '/inventory-item-details', + Container.get(InventoryDetailsController).router(), + ); return router; } } diff --git a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index c8bca9cc8..0d5ca9bec 100644 --- a/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -33,7 +33,6 @@ export default class BalanceSheetStatementController extends BaseFinancialReport get balanceSheetValidationSchema(): ValidationChain[] { return [ ...this.sheetNumberFormatValidationSchema, - query('accounting_method').optional().isIn(['cash', 'accural']), query('from_date').optional(), query('to_date').optional(), diff --git a/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts b/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts new file mode 100644 index 000000000..87235b609 --- /dev/null +++ b/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts @@ -0,0 +1,120 @@ +import { Inject, Service } from 'typedi'; +import { query } from 'express-validator'; +import { + NextFunction, + Router, + Request, + Response, + ValidationChain, +} from 'express'; +import BaseFinancialReportController from '../BaseFinancialReportController'; +import CashFlowStatementService from 'services/FinancialStatements/CashFlow/CashFlowService'; +import { ICashFlowStatementDOO, ICashFlowStatement } from 'interfaces'; +import CashFlowTable from 'services/FinancialStatements/CashFlow/CashFlowTable'; +import HasTenancyService from 'services/Tenancy/TenancyService'; + +@Service() +export default class CashFlowController extends BaseFinancialReportController { + @Inject() + cashFlowService: CashFlowStatementService; + + @Inject() + tenancy: HasTenancyService; + + /** + * Router constructor. + */ + router() { + const router = Router(); + + router.get( + '/', + this.cashflowValidationSchema, + this.validationResult, + this.asyncMiddleware(this.cashFlow.bind(this)) + ); + return router; + } + + /** + * Balance sheet validation schecma. + * @returns {ValidationChain[]} + */ + get cashflowValidationSchema(): ValidationChain[] { + return [ + ...this.sheetNumberFormatValidationSchema, + query('from_date').optional(), + query('to_date').optional(), + query('display_columns_type').optional().isIn(['date_periods', 'total']), + query('display_columns_by') + .optional({ nullable: true, checkFalsy: true }) + .isIn(['year', 'month', 'week', 'day', 'quarter']), + query('none_zero').optional().isBoolean().toBoolean(), + query('none_transactions').optional().isBoolean().toBoolean(), + ]; + } + + /** + * Retrieve the cashflow statment to json response. + * @param {ICashFlowStatement} cashFlow - + */ + private transformJsonResponse(cashFlowDOO: ICashFlowStatementDOO) { + const { data, query, meta } = cashFlowDOO; + + return { + data: this.transfromToResponse(data), + query: this.transfromToResponse(query), + meta: this.transfromToResponse(meta), + }; + } + + /** + * Transformes the report statement to table rows. + * @param {ITransactionsByVendorsStatement} statement - + * + */ + private transformToTableRows(cashFlowDOO: ICashFlowStatementDOO, tenantId: number) { + const i18n = this.tenancy.i18n(tenantId); + const cashFlowTable = new CashFlowTable(cashFlowDOO, i18n); + + return { + table: { + data: cashFlowTable.tableRows(), + columns: cashFlowTable.tableColumns(), + }, + query: this.transfromToResponse(cashFlowDOO.query), + meta: this.transfromToResponse(cashFlowDOO.meta), + }; + } + + /** + * Retrieve the cash flow statment. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Response} + */ + async cashFlow(req: Request, res: Response, next: NextFunction) { + const { tenantId, settings } = req; + const filter = { + ...this.matchedQueryData(req), + }; + + try { + const cashFlow = await this.cashFlowService.cashFlow(tenantId, filter); + + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); + + switch (acceptType) { + case 'application/json+table': + return res.status(200).send(this.transformToTableRows(cashFlow, tenantId)); + case 'json': + default: + return res.status(200).send(this.transformJsonResponse(cashFlow)); + } + } catch (error) { + next(error); + } + } +} diff --git a/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts b/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts new file mode 100644 index 000000000..d40d9e585 --- /dev/null +++ b/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts @@ -0,0 +1,127 @@ +import { Inject, Service } from 'typedi'; +import { query } from 'express-validator'; +import { + NextFunction, + Router, + Request, + Response, + ValidationChain, +} from 'express'; +import BaseController from 'api/controllers/BaseController'; +import InventoryDetailsService from 'services/FinancialStatements/InventoryDetails/InventoryDetailsService'; +import InventoryDetailsTable from 'services/FinancialStatements/InventoryDetails/InventoryDetailsTable'; +import HasTenancyService from 'services/Tenancy/TenancyService'; + +@Service() +export default class InventoryDetailsController extends BaseController { + @Inject() + inventoryDetailsService: InventoryDetailsService; + + @Inject() + tenancy: HasTenancyService; + + /** + * Router constructor. + */ + router() { + const router = Router(); + + router.get( + '/', + this.validationSchema, + this.validationResult, + this.asyncMiddleware(this.inventoryDetails.bind(this)) + ); + return router; + } + + /** + * Balance sheet validation schecma. + * @returns {ValidationChain[]} + */ + get validationSchema(): ValidationChain[] { + return [ + query('number_format.precision') + .optional() + .isInt({ min: 0, max: 5 }) + .toInt(), + query('number_format.divide_on_1000').optional().isBoolean().toBoolean(), + query('number_format.negative_format') + .optional() + .isIn(['parentheses', 'mines']) + .trim() + .escape(), + query('from_date').optional(), + query('to_date').optional(), + query('none_zero').optional().isBoolean().toBoolean(), + query('none_transactions').optional().isBoolean().toBoolean(), + ]; + } + + /** + * Retrieve the cashflow statment to json response. + * @param {ICashFlowStatement} cashFlow - + */ + private transformJsonResponse(inventoryDetails) { + const { data, query, meta } = inventoryDetails; + + return { + data: this.transfromToResponse(data), + query: this.transfromToResponse(query), + meta: this.transfromToResponse(meta), + }; + } + + /** + * Transformes the report statement to table rows. + */ + private transformToTableRows(inventoryDetails, tenantId: number) { + const i18n = this.tenancy.i18n(tenantId); + const inventoryDetailsTable = new InventoryDetailsTable(inventoryDetails, i18n); + + return { + table: { + data: inventoryDetailsTable.tableData(), + columns: inventoryDetailsTable.tableColumns(), + }, + query: this.transfromToResponse(inventoryDetails.query), + meta: this.transfromToResponse(inventoryDetails.meta), + }; + } + + /** + * Retrieve the cash flow statment. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Response} + */ + async inventoryDetails(req: Request, res: Response, next: NextFunction) { + const { tenantId, settings } = req; + const filter = { + ...this.matchedQueryData(req), + }; + + try { + const inventoryDetails = + await this.inventoryDetailsService.inventoryDetails(tenantId, filter); + + const accept = this.accepts(req); + const acceptType = accept.types(['json', 'application/json+table']); + + switch (acceptType) { + case 'application/json+table': + return res + .status(200) + .send(this.transformToTableRows(inventoryDetails, tenantId)); + case 'json': + default: + return res + .status(200) + .send(this.transformJsonResponse(inventoryDetails)); + } + } catch (error) { + next(error); + } + } +} diff --git a/server/src/api/index.ts b/server/src/api/index.ts index 5b4fd7d9d..649e67f0a 100644 --- a/server/src/api/index.ts +++ b/server/src/api/index.ts @@ -1,5 +1,6 @@ import { Router } from 'express'; import { Container } from 'typedi'; +import i18n from 'i18n'; // Middlewares import JWTAuth from 'api/middleware/jwtAuth'; @@ -47,6 +48,7 @@ export default () => { // - Global routes. // --------------------------- + app.use(i18n.init); app.use(I18nMiddleware); app.use('/auth', Container.get(Authentication).router()); diff --git a/server/src/api/middleware/I18nMiddleware.ts b/server/src/api/middleware/I18nMiddleware.ts index 027084f19..d354464d2 100644 --- a/server/src/api/middleware/I18nMiddleware.ts +++ b/server/src/api/middleware/I18nMiddleware.ts @@ -11,5 +11,6 @@ export default (req: Request, res: Response, next: NextFunction) => { } Logger.info('[i18n_middleware] set locale language to i18n.', { language, user: req.user }); i18n.setLocale(req, language); + next(); }; \ No newline at end of file diff --git a/server/src/api/middleware/TenantDependencyInjection.ts b/server/src/api/middleware/TenantDependencyInjection.ts index c8c447bca..18e39010d 100644 --- a/server/src/api/middleware/TenantDependencyInjection.ts +++ b/server/src/api/middleware/TenantDependencyInjection.ts @@ -6,6 +6,7 @@ import TenantsManagerService from 'services/Tenancy/TenantsManager'; export default (req: Request, tenant: ITenant) => { const { id: tenantId, organizationId } = tenant; + const tenantServices = Container.get(TenancyService); const tenantsManager = Container.get(TenantsManagerService); @@ -17,7 +18,9 @@ export default (req: Request, tenant: ITenant) => { const repositories = tenantServices.repositories(tenantId) const cacheInstance = tenantServices.cache(tenantId); - tenantServices.setI18nLocals(tenantId, { __: req.__ }); + const tenantContainer = tenantServices.tenantContainer(tenantId); + + tenantContainer.set('i18n', { __: req.__ }); req.knex = knexInstance; req.organizationId = organizationId; diff --git a/server/src/data/AccountTypes.ts b/server/src/data/AccountTypes.ts index f3b108627..ceef16bb1 100644 --- a/server/src/data/AccountTypes.ts +++ b/server/src/data/AccountTypes.ts @@ -3,9 +3,9 @@ export const ACCOUNT_TYPE = { BANK: 'bank', ACCOUNTS_RECEIVABLE: 'accounts-receivable', INVENTORY: 'inventory', - OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET', + OTHER_CURRENT_ASSET: 'other-current-asset', FIXED_ASSET: 'fixed-asset', - NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET', + NON_CURRENT_ASSET: 'none-current-asset', ACCOUNTS_PAYABLE: 'accounts-payable', CREDIT_CARD: 'credit-card', @@ -25,7 +25,7 @@ export const ACCOUNT_TYPE = { export const ACCOUNT_PARENT_TYPE = { CURRENT_ASSET: 'current-asset', FIXED_ASSET: 'fixed-asset', - NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET', + NON_CURRENT_ASSET: 'non-current-asset', CURRENT_LIABILITY: 'current-liability', LOGN_TERM_LIABILITY: 'long-term-liability', diff --git a/server/src/database/migrations/20200722164255_create_inventory_transaction_meta_table.js b/server/src/database/migrations/20200722164255_create_inventory_transaction_meta_table.js new file mode 100644 index 000000000..15f348a17 --- /dev/null +++ b/server/src/database/migrations/20200722164255_create_inventory_transaction_meta_table.js @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.createTable('inventory_transaction_meta', (table) => { + table.increments('id'); + table.string('transaction_number'); + table.text('description'); + table.integer('inventory_transaction_id').unsigned(); + }); + }; + + exports.down = function (knex) {}; + \ No newline at end of file diff --git a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js index 7f45dbdef..d490cbcc7 100644 --- a/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js +++ b/server/src/database/migrations/20200810121807_create_inventory_cost_lot_tracker_table.js @@ -15,6 +15,7 @@ exports.up = function (knex) { table.integer('entry_id').unsigned().index(); table.integer('cost_account_id').unsigned(); + table.integer('inventory_transaction_id').unsigned().index(); table.datetime('created_at').index(); }); diff --git a/server/src/database/seeds/core/20190423085242_seed_accounts.js b/server/src/database/seeds/core/20190423085242_seed_accounts.js index d33868ec9..3c36f60f0 100644 --- a/server/src/database/seeds/core/20190423085242_seed_accounts.js +++ b/server/src/database/seeds/core/20190423085242_seed_accounts.js @@ -1,15 +1,24 @@ import Container from 'typedi'; -import { get } from 'lodash'; -import TenancyService from 'services/Tenancy/TenancyService' +import { get } from 'lodash'; +import TenancyService from 'services/Tenancy/TenancyService'; import AccountsData from '../data/accounts'; exports.up = function (knex) { + const tenancyService = Container.get(TenancyService); + const i18n = tenancyService.i18n(knex.userParams.tenantId); + + const data = AccountsData.map((account) => { + return { + ...account, + name: i18n.__(account.name), + description: i18n.__(account.description), + }; + }); + return knex('accounts').then(async () => { // Inserts seed entries. - return knex('accounts').insert([ ...AccountsData ]); + return knex('accounts').insert(data); }); }; -exports.down = function (knex) { - -}; +exports.down = function (knex) {}; diff --git a/server/src/database/seeds/core/20200810121807_seed_views.js b/server/src/database/seeds/core/20200810121807_seed_views.js index 941880b92..0ce08584f 100644 --- a/server/src/database/seeds/core/20200810121807_seed_views.js +++ b/server/src/database/seeds/core/20200810121807_seed_views.js @@ -33,44 +33,44 @@ exports.up = (knex) => { { id: 13, name: i18n.__('Published'), slug: 'published', roles_logic_expression: '1', resource_model: 'Expense', predefined: true, }, // Sales invoices. - { id: 16, name: 'Draft', slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true, }, - { id: 17, name: 'Delivered', slug: 'delivered', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, - { id: 18, name: 'Unpaid', slug: 'unpaid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, - { id: 19, name: 'Overdue', slug: 'overdue', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, - { id: 20, name: 'Partially paid', slug: 'partially-paid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, - { id: 21, name: 'Paid', slug: 'paid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, + { id: 16, name: i18n.__('Draft'), slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true, }, + { id: 17, name: i18n.__('Delivered'), slug: 'delivered', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, + { id: 18, name: i18n.__('Unpaid'), slug: 'unpaid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, + { id: 19, name: i18n.__('Overdue'), slug: 'overdue', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, + { id: 20, name: i18n.__('Partially paid'), slug: 'partially-paid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, + { id: 21, name: i18n.__('Paid'), slug: 'paid', roles_logic_expression: '1', resource_model: 'SaleInvoice', predefined: true }, // Bills. - { id: 22, name: 'Draft', slug: 'draft', roles_logic_expression: '1', resource_model: 'Bill', predefined: true, }, - { id: 23, name: 'Opened', slug: 'opened', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, - { id: 24, name: 'Unpaid', slug: 'unpaid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, - { id: 25, name: 'Overdue', slug: 'overdue', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, - { id: 26, name: 'Partially paid', slug: 'partially-paid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, - { id: 27, name: 'Paid', slug: 'paid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, + { id: 22, name: i18n.__('Draft'), slug: 'draft', roles_logic_expression: '1', resource_model: 'Bill', predefined: true, }, + { id: 23, name: i18n.__('Opened'), slug: 'opened', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, + { id: 24, name: i18n.__('Unpaid'), slug: 'unpaid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, + { id: 25, name: i18n.__('Overdue'), slug: 'overdue', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, + { id: 26, name: i18n.__('Partially paid'), slug: 'partially-paid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, + { id: 27, name: i18n.__('Paid'), slug: 'paid', roles_logic_expression: '1', resource_model: 'Bill', predefined: true }, // Sale estimate. - { id: 28, name: 'Draft', slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, - { id: 29, name: 'Delivered', slug: 'delivered', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, - { id: 30, name: 'Approved', slug: 'approved', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, - { id: 31, name: 'Rejected', slug: 'rejected', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, - { id: 32, name: 'Invoiced', slug: 'invoiced', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, - { id: 33, name: 'Expired', slug: 'expired', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 28, name: i18n.__('Draft'), slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 29, name: i18n.__('Delivered'), slug: 'delivered', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 30, name: i18n.__('Approved'), slug: 'approved', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 31, name: i18n.__('Rejected'), slug: 'rejected', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 32, name: i18n.__('Invoiced'), slug: 'invoiced', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, + { id: 33, name: i18n.__('Expired'), slug: 'expired', roles_logic_expression: '1', resource_model: 'SaleEstimate', predefined: true }, // Sale receipts. - { id: 34, name: 'Draft', slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleReceipt', predefined: true }, - { id: 35, name: 'Closed', slug: 'closed', roles_logic_expression: '1', resource_model: 'SaleReceipt', predefined: true }, + { id: 34, name: i18n.__('Draft'), slug: 'draft', roles_logic_expression: '1', resource_model: 'SaleReceipt', predefined: true }, + { id: 35, name: i18n.__('Closed'), slug: 'closed', roles_logic_expression: '1', resource_model: 'SaleReceipt', predefined: true }, // Customers - { id: 36, name: 'Active', slug: 'active', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, - { id: 37, name: 'Inactive', slug: 'inactive', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, - { id: 38, name: 'Overdue', slug: 'overdue', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, - { id: 39, name: 'Unpaid', slug: 'inpaid', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, + { id: 36, name: i18n.__('Active'), slug: 'active', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, + { id: 37, name: i18n.__('Inactive'), slug: 'inactive', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, + { id: 38, name: i18n.__('Overdue'), slug: 'overdue', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, + { id: 39, name: i18n.__('Unpaid'), slug: 'inpaid', roles_logic_expression: '1', resource_model: 'Customer', predefined: true }, // Vendors - { id: 40, name: 'Active', slug: 'active', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, - { id: 41, name: 'Inactive', slug: 'inactive', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, - { id: 42, name: 'Overdue', slug: 'overdue', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, - { id: 43, name: 'Unpaid', slug: 'overdue', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, + { id: 40, name: i18n.__('Active'), slug: 'active', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, + { id: 41, name: i18n.__('Inactive'), slug: 'inactive', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, + { id: 42, name: i18n.__('Overdue'), slug: 'overdue', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, + { id: 43, name: i18n.__('Unpaid'), slug: 'overdue', roles_logic_expression: '1', resource_model: 'Vendor', predefined: true }, ]); }); }; diff --git a/server/src/interfaces/Account.ts b/server/src/interfaces/Account.ts index 39303e829..60cf1dbee 100644 --- a/server/src/interfaces/Account.ts +++ b/server/src/interfaces/Account.ts @@ -33,7 +33,13 @@ export interface IAccountsTransactionsFilter { } export interface IAccountTransaction { - + credit: number; + debit: number; + accountId: number; + contactId: number; + date: string|Date; + referenceNumber: string; + account: IAccount; } export interface IAccountResponse extends IAccount { diff --git a/server/src/interfaces/CashFlow.ts b/server/src/interfaces/CashFlow.ts new file mode 100644 index 000000000..ade9c4aaa --- /dev/null +++ b/server/src/interfaces/CashFlow.ts @@ -0,0 +1,200 @@ +import { INumberFormatQuery } from './FinancialStatements'; +import { IAccount } from './Account'; +import { ILedger } from './Ledger'; +import { ITableRow } from './Table'; + +export interface ICashFlowStatementQuery { + fromDate: Date | string; + toDate: Date | string; + displayColumnsBy: string; + displayColumnsType: string; + noneZero: boolean; + noneTransactions: boolean; + numberFormat: INumberFormatQuery; + basis: string; +} + +export interface ICashFlowStatementTotal { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface ICashFlowStatementTotalPeriod { + fromDate: Date; + toDate: Date; + total: ICashFlowStatementTotal; +} + +export interface ICashFlowStatementCommonSection { + id: string; + label: string; + total: ICashFlowStatementTotal; + footerLabel?: string; +} + +export interface ICashFlowStatementAccountMeta { + id: number; + label: string; + code: string; + total: ICashFlowStatementTotal; + accountType: string; + adjusmentType: string; + sectionType: ICashFlowStatementSectionType.ACCOUNT; +} + +export enum ICashFlowStatementSectionType { + REGULAR = 'REGULAR', + AGGREGATE = 'AGGREGATE', + NET_INCOME = 'NET_INCOME', + ACCOUNT = 'ACCOUNT', + ACCOUNTS = 'ACCOUNTS', + TOTAL = 'TOTAL', + CASH_AT_BEGINNING = 'CASH_AT_BEGINNING', +} + +export interface ICashFlowStatementAccountSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.ACCOUNTS; + children: ICashFlowStatementAccountMeta[]; + total: ICashFlowStatementTotal; +} + +export interface ICashFlowStatementNetIncomeSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.NET_INCOME; +} + +export interface ICashFlowStatementTotalSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.TOTAL; +} + +export type ICashFlowStatementSection = + | ICashFlowStatementAccountSection + | ICashFlowStatementNetIncomeSection + | ICashFlowStatementTotalSection + | ICashFlowStatementCommonSection; + +export interface ICashFlowStatementColumn {} +export interface ICashFlowStatementMeta { + isCostComputeRunning: boolean; + organizationName: string; + baseCurrency: string; +} + +export interface ICashFlowStatementDOO { + data: ICashFlowStatementData; + meta: ICashFlowStatementMeta; + query: ICashFlowStatementQuery; +} + +export interface ICashFlowStatementService { + cashFlow( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise; +} + +// CASH FLOW SCHEMA TYPES. +// ----------------------------- +export interface ICashFlowSchemaCommonSection { + id: string; + label: string; + children: ICashFlowSchemaSection[]; + footerLabel?: string; +} + +export enum CASH_FLOW_ACCOUNT_RELATION { + MINES = 'mines', + PLUS = 'plus', +} + +export enum CASH_FLOW_SECTION_ID { + NET_INCOME = 'NET_INCOME', + OPERATING = 'OPERATING', + OPERATING_ACCOUNTS = 'OPERATING_ACCOUNTS', + INVESTMENT = 'INVESTMENT', + FINANCIAL = 'FINANCIAL', + + NET_OPERATING = 'NET_OPERATING', + NET_INVESTMENT = 'NET_INVESTMENT', + NET_FINANCIAL = 'NET_FINANCIAL', + + CASH_BEGINNING_PERIOD = 'CASH_BEGINNING_PERIOD', + CASH_END_PERIOD = 'CASH_END_PERIOD', + NET_CASH_INCREASE = 'NET_CASH_INCREASE', +} + +export interface ICashFlowSchemaAccountsSection + extends ICashFlowSchemaCommonSection { + sectionType: ICashFlowStatementSectionType.ACCOUNT; + accountsRelations: ICashFlowSchemaAccountRelation[]; +} + +export interface ICashFlowSchemaTotalSection + extends ICashFlowStatementCommonSection { + sectionType: ICashFlowStatementSectionType.TOTAL; + equation: string; +} + +export type ICashFlowSchemaSection = + | ICashFlowSchemaAccountsSection + | ICashFlowSchemaTotalSection + | ICashFlowSchemaCommonSection; + +export type ICashFlowStatementData = ICashFlowSchemaSection[]; + +export interface ICashFlowSchemaAccountRelation { + type: string; + direction: CASH_FLOW_ACCOUNT_RELATION.PLUS; +} + +export interface ICashFlowSchemaSectionAccounts + extends ICashFlowStatementCommonSection { + type: ICashFlowStatementSectionType.ACCOUNT; + accountsRelations: ICashFlowSchemaAccountRelation[]; +} + +export interface ICashFlowSchemaSectionTotal { + type: ICashFlowStatementSectionType.TOTAL; + totalEquation: string; +} + +export interface ICashFlowDatePeriod { + fromDate: ICashFlowDate; + toDate: ICashFlowDate; + total: ICashFlowStatementTotal; +} + +export interface ICashFlowDate { + formattedDate: string; + date: Date; +} + +export interface ICashFlowStatement { + /** + * Constructor method. + * @constructor + */ + constructor( + accounts: IAccount[], + ledger: ILedger, + cashLedger: ILedger, + netIncomeLedger: ILedger, + query: ICashFlowStatementQuery, + baseCurrency: string + ): void; + + reportData(): ICashFlowStatementData; +} + +export interface ICashFlowTable { + constructor(reportStatement: ICashFlowStatement): void; + tableRows(): ITableRow[]; +} + +export interface IDateRange { + fromDate: Date; + toDate: Date; +} diff --git a/server/src/interfaces/InventoryDetails.ts b/server/src/interfaces/InventoryDetails.ts new file mode 100644 index 000000000..69680f04f --- /dev/null +++ b/server/src/interfaces/InventoryDetails.ts @@ -0,0 +1,90 @@ +import { + INumberFormatQuery, +} from './FinancialStatements'; + +export interface IInventoryDetailsQuery { + fromDate: Date | string; + toDate: Date | string; + numberFormat: INumberFormatQuery; + noneTransactions: boolean; +} + +export interface IInventoryDetailsNumber { + number: number; + formattedNumber: string; +} + +export interface IInventoryDetailsMoney { + amount: number; + formattedAmount: string; + currencyCode: string; +} + +export interface IInventoryDetailsDate { + date: Date; + formattedDate: string; +} + +export interface IInventoryDetailsOpening { + nodeType: 'OPENING_ENTRY'; + date: IInventoryDetailsDate; + quantity: IInventoryDetailsNumber; + value: IInventoryDetailsNumber; +} + +export interface IInventoryDetailsClosing extends IInventoryDetailsOpening { + nodeType: 'CLOSING_ENTRY'; +} + +export interface IInventoryDetailsItem { + id: number; + nodeType: string; + name: string; + code: string; + children: ( + | IInventoryDetailsItemTransaction + | IInventoryDetailsOpening + | IInventoryDetailsClosing + )[]; +} + +export interface IInventoryDetailsItemTransaction { + nodeType: string; + date: IInventoryDetailsDate; + transactionType: string; + transactionNumber: string; + + quantityMovement: IInventoryDetailsNumber; + valueMovement: IInventoryDetailsNumber; + + quantity: IInventoryDetailsNumber; + total: IInventoryDetailsNumber; + cost: IInventoryDetailsNumber; + value: IInventoryDetailsNumber; + profitMargin: IInventoryDetailsNumber; + + rate: IInventoryDetailsNumber; + + runningQuantity: IInventoryDetailsNumber; + runningValuation: IInventoryDetailsNumber; + + direction: string; +} + +export type IInventoryDetailsNode = + | IInventoryDetailsItem + | IInventoryDetailsItemTransaction; +export type IInventoryDetailsData = IInventoryDetailsItem[]; + + +export interface IInventoryItemDetailMeta { + isCostComputeRunning: boolean; + organizationName: string; + baseCurrency: string; +} + +export interface IInvetoryItemDetailDOO { + data: IInventoryDetailsData; + query: IInventoryDetailsQuery; + meta: IInventoryItemDetailMeta; +} \ No newline at end of file diff --git a/server/src/interfaces/InventoryTransaction.ts b/server/src/interfaces/InventoryTransaction.ts index 47e4d4219..f56666bc3 100644 --- a/server/src/interfaces/InventoryTransaction.ts +++ b/server/src/interfaces/InventoryTransaction.ts @@ -1,38 +1,51 @@ - export type TInventoryTransactionDirection = 'IN' | 'OUT'; export interface IInventoryTransaction { - id?: number, - date: Date|string, - direction: TInventoryTransactionDirection, - itemId: number, + id?: number; + date: Date | string; + direction: TInventoryTransactionDirection; + itemId: number; + quantity: number; + rate: number; + transactionType: string; + transcationTypeFormatted: string; + transactionId: number; + entryId: number; + costAccountId: number; + meta?: IInventoryTransactionMeta; + costLotAggregated?: IInventoryCostLotAggregated; + createdAt?: Date; + updatedAt?: Date; +} + +export interface IInventoryTransactionMeta { + id?: number; + transactionNumber: string; + description: string; +} + +export interface IInventoryCostLotAggregated { + cost: number, quantity: number, - rate: number, - transactionType: string, - transactionId: number, - entryId: number, - costAccountId: number, - createdAt?: Date, - updatedAt?: Date, }; export interface IInventoryLotCost { - id?: number, - date: Date, - direction: string, - itemId: number, - quantity: number, - rate: number, - remaining: number, - cost: number, - transactionType: string, - transactionId: number, - costAccountId: number, - entryId: number, - createdAt: Date, -}; + id?: number; + date: Date; + direction: string; + itemId: number; + quantity: number; + rate: number; + remaining: number; + cost: number; + transactionType: string; + transactionId: number; + costAccountId: number; + entryId: number; + createdAt: Date; +} export interface IItemsQuantityChanges { - itemId: number, - balanceChange: number, -}; \ No newline at end of file + itemId: number; + balanceChange: number; +} diff --git a/server/src/interfaces/Ledger.ts b/server/src/interfaces/Ledger.ts index e3dd19288..938a997b5 100644 --- a/server/src/interfaces/Ledger.ts +++ b/server/src/interfaces/Ledger.ts @@ -2,6 +2,7 @@ export interface ILedger { entries: ILedgerEntry[]; getEntries(): ILedgerEntry[]; + whereAccountId(accountId: number): ILedger; whereContactId(contactId: number): ILedger; whereFromDate(fromDate: Date | string): ILedger; whereToDate(toDate: Date | string): ILedger; @@ -15,6 +16,6 @@ export interface ILedgerEntry { accountNormal: string; contactId?: number; date: Date | string; - transactionType: string, - transactionNumber: string, + transactionType?: string, + transactionNumber?: string, } diff --git a/server/src/interfaces/Table.ts b/server/src/interfaces/Table.ts index f04d844e0..944ace6db 100644 --- a/server/src/interfaces/Table.ts +++ b/server/src/interfaces/Table.ts @@ -12,4 +12,14 @@ export interface ITableCell { export type ITableRow = { rows: ITableCell[]; -}; \ No newline at end of file +}; + +export interface ITableColumn { + key: string, + label: string, +} + +export interface ITable { + columns: ITableColumn[], + data: ITableRow[], +} \ No newline at end of file diff --git a/server/src/interfaces/TransactionsByContacts.ts b/server/src/interfaces/TransactionsByContacts.ts index 9f4569ba0..82a38002a 100644 --- a/server/src/interfaces/TransactionsByContacts.ts +++ b/server/src/interfaces/TransactionsByContacts.ts @@ -25,8 +25,8 @@ export interface ITransactionsByContactsContact { } export interface ITransactionsByContactsFilter { - fromDate: Date; - toDate: Date; + fromDate: Date|string; + toDate: Date|string; numberFormat: INumberFormatQuery; noneTransactions: boolean; noneZero: boolean; diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index 090f22f32..388668041 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -50,4 +50,10 @@ export * from './TransactionsByCustomers'; export * from './TransactionsByContacts'; export * from './TransactionsByVendors'; export * from './Table'; -export * from './Ledger'; \ No newline at end of file +export * from './Ledger'; +export * from './CashFlow'; +export * from './InventoryDetails'; + +export interface I18nService { + __: (input: string) => string; +} \ No newline at end of file diff --git a/server/src/locales/ar.json b/server/src/locales/ar.json index d23481838..2940a3302 100644 --- a/server/src/locales/ar.json +++ b/server/src/locales/ar.json @@ -1,4 +1,161 @@ { - "Empty": "", - "Hello": "مرحبا" + "Petty Cash": "العهدة", + "Cash": "النقدية", + "Bank": "المصرف", + "Other Income": "إيرادات اخري", + "Interest Income": "إيرادات الفوائد", + "Opening Balance": "رصيد", + "Depreciation Expense": "مصاريف الاهلاك", + "Interest Expense": "مصروفات الفوائد", + "Sales of Product Income": "مبيعات دخل المنتجات", + "Inventory Asset": "المخزون", + "Cost of Goods Sold (COGS)": "تكلفة البضائع المباعة (COGS)", + "Cost of Goods Sold": "تكلفة البضاعة المباعة", + "Accounts Payable": "الذمم الدائنة", + "Other Expense": "مصاريف أخرى", + "Payroll Expenses": "مصاريف المرتبات", + "Fixed Asset": "أصول ثابتة", + "Credit Card": "بطاقة إئتمان", + "Non-Current Asset": "أصول غير متداولة", + "Current Asset": "أصول متداولة", + "Other Asset": "أصول اخري", + "Long Term Liability": "التزامات طويلة الاجل", + "Current Liability": "التزامات قصيرة الاجل", + "Other Liability": "التزمات اخري", + "Equity": "حقوق الملكية", + "Expense": "مصروف", + "Income": "دخل", + "Accounts Receivable (A/R)": "الذمم المدينة", + "Accounts Receivable": "الذمم المدينة", + "Accounts Payable (A/P)": "الذمم الدائنة", + "Inactive": "غير نشط", + "Other Current Asset": "أصول متداولة اخرى", + "Tax Payable": "الضريبة المستحقة", + "Other Current Liability": "التزامات قصيرة الأجر اخرى", + "Non-Current Liability": "التزامات طويلة الأجر", + "Assets": "أصول", + "Liabilities": "الالتزمات", + "Account name": "أسم الحساب", + "Account type": "نوع الحساب", + "Account normal": "حساب عادي", + "Description": "وصف", + "Account code": "رمز الحساب", + "Currency": "عملة", + "Balance": "توازن", + "Active": "نشيط", + "Created at": "أنشئت في", + "fixed_asset": "أصل ثابت", + "Journal": "قيد", + "Reconciliation": "تسوية", + "Credit": "دائن", + "Debit": "مدين", + "Interest": "فائدة", + "Depreciation": "اهلاك", + "Payroll": "كشف رواتب", + "Type": "نوع", + "Name": "الأسم", + "Sellable": "قابل للبيع", + "Purchasable": "قابل للشراء", + "Sell price": "سعر البيع", + "Cost price": "سعر الكلفة", + "User": "المستخدم", + "Category": "تصنيف", + "Note": "ملحوظة", + "Quantity on hand": "كمية في اليد", + "Purchase description": "وصف الشراء", + "Sell description": "وصف البيع", + "Sell account": "حساب البيع", + "Cost account": "حساب التكلفة", + "Inventory account": "حساب المخزون", + "Payment date": "تاريخ الدفع", + "Payment account": "حساب الدفع", + "Amount": "كمية", + "Reference No.": "رقم المرجع.", + "Published": "نشرت", + "Journal number": "رقم القيد", + "Status": "حالة", + "Journal type": "نوع القيد", + "Date": "تاريخ", + "Asset": "أصل", + "Liability": "مسؤلية", + "First-in first-out (FIFO)": "الوارد أولاً يصرف أولاً (FIFO)", + "Last-in first-out (LIFO)": "الوارد أخيرًا يصرف أولاً (LIFO)", + "Average rate": "المعدل المتوسط", + "Total": "الإجمالي", + "Transaction type": "نوع المعاملة", + "Transaction #": "عملية #", + "Running Value": "القيمة الجارية", + "Running quantity": "الكمية الجارية", + "Profit Margin": "هامش الربح", + "Value": "القيمة", + "Rate": "السعر", + "OPERATING ACTIVITIES": "أنشطة التشغيل", + "FINANCIAL ACTIVITIES": "الأنشطة المالية", + "Net income": "صافي الدخل", + "Adjustments net income by operating activities.": "تعديلات صافي الدخل حسب الأنشطة التشغيلية.", + "Net cash provided by operating activities": "صافي النقد الناتج من أنشطة التشغيل", + "Net cash provided by investing activities": "صافي النقد المقدم من أنشطة الاستثمار", + "Net cash provided by financing activities": "صافي النقد الناتج عن أنشطة التمويل", + "Cash at beginning of period": "النقدية في بداية الفترة", + "NET CASH INCREASE FOR PERIOD": "زيادة صافي النقد للفترة", + "CASH AT END OF PERIOD": "النقد في نهاية الفترة", + "Expenses": "مصاريف", + "Services": "خدمات", + "Inventory": "المخزون", + "Non-Inventory": "غير متعلق بالمخزون", + "Draft": "مسودة", + "Delivered": "تم التوصيل", + "Overdue": "متأخر", + "Partially paid": "المدفوعة جزئيا", + "Paid": "مدفوع", + "Opened": "افتتح", + "Unpaid": "غير مدفوعة", + "Approved": "وافق", + "Rejected": "مرفوض", + "Invoiced": "مفوترة", + "Expired": "منتهي الصلاحية", + "Closed": "مغلق", + "Manual journal": "قيد اليدوي", + "Inventory adjustment": "تسوية المخزون", + "Customer opening balance": "الرصيد الافتتاحي للزبون", + "Vendor opening balance": "رصيد افتتاحي للمورد", + "Payment made": "سند الزبون", + "Bill": "فاتورة الشراء", + "Payment receive": "استلام الدفع", + "Sale receipt": "إيصال البيع", + "Sale invoice": "فاتورة البيع", + "Quantity": "الكمية", + "Bank Account": "حساب البنك", + "Saving Bank Account": "حساب التوفير البنكي", + "Undeposited Funds": "الأموال غير المودعة", + "Computer Equipment": "معدات كمبيوتر", + "Office Equipment": "معدات مكتبية", + "Uncategorized Income": "الدخل غير مصنف", + "Sales of Service Income": "دخل مبيعات الخدمات", + "Bank Fees and Charges": "رسوم المصرفية", + "Exchange Gain or Loss": "ربح أو خسارة فروقات الصرف", + "Rent": "إيجار", + "Office expenses": "مصاريف المكتب", + "Other Expenses": "مصاريف اخري", + "Drawings": "السحوبات", + "Owner's Equity": "حقوق الملكية", + "Opening Balance Equity": "الارصدة الافتتاحية ", + "Retained Earnings": "الأرباح المحتجزة", + "Sales Tax Payable": "ضريبة المبيعات المستحقة", + "Revenue Received in Advance": "الإيرادات المقبوضة مقدما", + "Opening Balance Liabilities": "رصيد الالتزامات الافتتاحي", + "Loan": "اقراض", + "Owner A Drawings": "مسحوبات المالك", + "An account that holds valuation of products or goods that availiable for sale.": "حساب يحمل قيم مخزون البضاعة أو السلع المتاحة للبيع.", + "Tracks the gain and losses of the exchange differences.": "يسجل مكاسب وخسائر فروق الصرف.", + "Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.": "يتم تسجيل أي رسوم مصرفية يتم فرضها في حساب الرسوم والمصروفات البنكية. ومن الأمثلة على ذلك رسوم صيانة الحساب المصرفي ورسوم المعاملات ورسوم الدفع المتأخر.", + "The income activities are not associated to the core business.": "لا ترتبط انشطة الدخل إلى الأعمال الأساسية.", + "Cash and cash equivalents": "النقد والنقد المكافئ", + "Inventories": "مخزون البضاعة", + "Other current assets": "الأصول متداولة الأخرى", + "Non-Current Assets": "أصول غير المتداولة", + "Current Liabilties": "التزامات متداولة", + "Long-Term Liabilities": "التزامات طويلة الاجل", + "Non-Current Liabilities": "التزامات غير متداولة", + "Liabilities and Equity": "التزامات وحقوق الملكية" } \ No newline at end of file diff --git a/server/src/locales/en.json b/server/src/locales/en.json index fc9fb381a..2ba5d9750 100644 --- a/server/src/locales/en.json +++ b/server/src/locales/en.json @@ -62,6 +62,7 @@ "Category": "Category", "Note": "Note", "Quantity on hand": "Quantity on hand", + "Quantity": "Quantity", "Purchase description": "Purchase description", "Sell description": "Sell description", "Sell account": "Sell account", @@ -71,7 +72,6 @@ "Payment account": "Payment account", "Amount": "Amount", "Reference No.": "Reference No.", - "Published": "Published", "Journal number": "Journal number", "Status": "Status", "Journal type": "Journal type", @@ -80,5 +80,82 @@ "Liability": "Liability", "First-in first-out (FIFO)": "First-in first-out (FIFO)", "Last-in first-out (LIFO)": "Last-in first-out (LIFO)", - "Average rate": "Average rate" + "Average rate": "Average rate", + "Total": "Total", + "Transaction type": "Transaction type", + "Transaction #": "Transaction #", + "Running Value": "Running Value", + "Running quantity": "Running quantity", + "Profit Margin": "Profit Margin", + "Value": "Value", + "Rate": "Rate", + "OPERATING ACTIVITIES": "OPERATING ACTIVITIES", + "FINANCIAL ACTIVITIES": "FINANCIAL ACTIVITIES", + "Net income": "Net income", + "Adjustments net income by operating activities.": "Adjustments net income by operating activities.", + "Net cash provided by operating activities": "Net cash provided by operating activities", + "Net cash provided by investing activities": "Net cash provided by investing activities", + "Net cash provided by financing activities": "Net cash provided by financing activities", + "Cash at beginning of period": "Cash at beginning of period", + "NET CASH INCREASE FOR PERIOD": "NET CASH INCREASE FOR PERIOD", + "CASH AT END OF PERIOD": "CASH AT END OF PERIOD", + "Expenses": "Expenses", + "Services": "Services", + "Inventory": "Inventory", + "Non-Inventory": "Non-Inventory", + "Draft": "Draft", + "Published": "Published", + "Delivered": "Delivered", + "Overdue": "Overdue", + "Partially paid": "Partially paid", + "Paid": "Paid", + "Opened": "Opened", + "Unpaid": "Unpaid", + "Approved": "Approved", + "Rejected": "Rejected", + "Invoiced": "Invoiced", + "Expired": "Expired", + "Closed": "Closed", + "Manual journal": "Manual journal", + "Inventory adjustment": "Inventory adjustment", + "Customer opening balance": "Customer opening balance", + "Vendor opening balance": "Vendor opening balance", + "Payment made": "Payment made", + "Bill": "Bill", + "Payment receive": "Payment receive", + "Sale receipt": "Sale receipt", + "Sale invoice": "Sale invoice", + "Bank Account": "Bank Account", + "Saving Bank Account": "Saving Bank Account", + "Undeposited Funds": "Undeposited Funds", + "Computer Equipment": "Computer Equipment", + "Office Equipment": "Office Equipment", + "Uncategorized Income": "Uncategorized Income", + "Sales of Service Income": "Sales of Service Income", + "Bank Fees and Charges": "Bank Fees and Charges", + "Exchange Gain or Loss": "Exchange Gain or Loss", + "Rent": "Rent", + "Office expenses": "Office expenses", + "Other Expenses": "Other Expenses", + "Drawings": "Drawings", + "Owner's Equity": "Owner's Equity", + "Opening Balance Equity": "Opening Balance Equity", + "Retained Earnings": "Retained Earnings", + "Sales Tax Payable": "Sales Tax Payable", + "Revenue Received in Advance": "Revenue Received in Advance", + "Opening Balance Liabilities": "Opening Balance Liabilities", + "Loan": "Loan", + "Owner A Drawings": "Owner A Drawings", + "An account that holds valuation of products or goods that availiable for sale.": "An account that holds valuation of products or goods that availiable for sale.", + "Tracks the gain and losses of the exchange differences.": "Tracks the gain and losses of the exchange differences.", + "Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.": "Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.", + "The income activities are not associated to the core business.": "The income activities are not associated to the core business.", + "Cash and cash equivalents": "Cash and cash equivalents", + "Inventories": "Inventories", + "Other current assets": "Other current assets", + "Non-Current Assets": "Non-Current Assets", + "Current Liabilties": "Current Liabilties", + "Long-Term Liabilities": "Long-Term Liabilities", + "Non-Current Liabilities": "Non-Current Liabilities", + "Liabilities and Equity": "Liabilities and Equity" } \ No newline at end of file diff --git a/server/src/models/AccountTransaction.js b/server/src/models/AccountTransaction.js index 0d615a294..0cf9e246a 100644 --- a/server/src/models/AccountTransaction.js +++ b/server/src/models/AccountTransaction.js @@ -1,4 +1,4 @@ -import { Model } from 'objection'; +import { Model, raw } from 'objection'; import moment from 'moment'; import TenantModel from 'models/TenantModel'; @@ -138,6 +138,21 @@ export default class AccountTransaction extends TenantModel { query.sum('credit as credit'); query.sum('debit as debit'); query.select('contactId'); + }, + creditDebitSummation(query) { + query.sum('credit as credit'); + query.sum('debit as debit'); + }, + groupByDateFormat(query, groupType = 'month') { + const groupBy = { + 'day': '%Y-%m-%d', + 'month': '%Y-%m', + 'year': '%Y', + }; + const dateFormat = groupBy[groupType]; + + query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date')); + query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`); } }; } diff --git a/server/src/models/InventoryTransaction.js b/server/src/models/InventoryTransaction.js index 87146ced0..8c2b10f4c 100644 --- a/server/src/models/InventoryTransaction.js +++ b/server/src/models/InventoryTransaction.js @@ -17,6 +17,33 @@ export default class InventoryTransaction extends TenantModel { return ['createdAt', 'updatedAt']; } + /** + * Retrieve formatted reference type. + * @return {string} + */ + get transcationTypeFormatted() { + return InventoryTransaction.getReferenceTypeFormatted(this.transactionType); + } + + /** + * Reference type formatted. + */ + static getReferenceTypeFormatted(referenceType) { + const mapped = { + 'SaleInvoice': 'Sale invoice', + 'SaleReceipt': 'Sale receipt', + 'PaymentReceive': 'Payment receive', + 'Bill': 'Bill', + 'BillPayment': 'Payment made', + 'VendorOpeningBalance': 'Vendor opening balance', + 'CustomerOpeningBalance': 'Customer opening balance', + 'InventoryAdjustment': 'Inventory adjustment', + 'ManualJournal': 'Manual journal', + 'Journal': 'Manual journal', + }; + return mapped[referenceType] || ''; + } + /** * Model modifiers. */ @@ -59,8 +86,47 @@ export default class InventoryTransaction extends TenantModel { static get relationMappings() { const Item = require('models/Item'); const ItemEntry = require('models/ItemEntry'); + const InventoryTransactionMeta = require('models/InventoryTransactionMeta'); + const InventoryCostLots = require('models/InventoryCostLotTracker'); return { + // Transaction meta. + meta: { + relation: Model.HasOneRelation, + modelClass: InventoryTransactionMeta.default, + join: { + from: 'inventory_transactions.id', + to: 'inventory_transaction_meta.inventoryTransactionId', + }, + }, + // Item cost aggregated. + itemCostAggregated: { + relation: Model.HasOneRelation, + modelClass: InventoryCostLots.default, + join: { + from: 'inventory_transactions.itemId', + to: 'inventory_cost_lot_tracker.itemId', + }, + filter(query) { + query.select('itemId'); + query.sum('cost as cost'); + query.sum('quantity as quantity'); + query.groupBy('itemId'); + }, + }, + costLotAggregated: { + relation: Model.HasOneRelation, + modelClass: InventoryCostLots.default, + join: { + from: 'inventory_transactions.id', + to: 'inventory_cost_lot_tracker.inventoryTransactionId', + }, + filter(query) { + query.sum('cost as cost'); + query.sum('quantity as quantity'); + query.groupBy('inventoryTransactionId'); + } + }, item: { relation: Model.BelongsToOneRelation, modelClass: Item.default, diff --git a/server/src/models/InventoryTransactionMeta.js b/server/src/models/InventoryTransactionMeta.js new file mode 100644 index 000000000..62a232b64 --- /dev/null +++ b/server/src/models/InventoryTransactionMeta.js @@ -0,0 +1,29 @@ +import { Model, raw } from 'objection'; +import TenantModel from 'models/TenantModel'; + +export default class InventoryTransactionMeta extends TenantModel { + /** + * Table name + */ + static get tableName() { + return 'inventory_transaction_meta'; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + const InventoryTransactions = require('models/InventoryTransaction'); + + return { + inventoryTransaction: { + relation: Model.BelongsToOneRelation, + modelClass: InventoryTransactions.default, + join: { + from: 'inventory_transaction_meta.inventoryTransactionId', + to: 'inventory_transactions.inventoryTransactionId' + } + } + }; + } +} diff --git a/server/src/services/Accounting/Ledger.ts b/server/src/services/Accounting/Ledger.ts index 93870dfdb..fc78e554f 100644 --- a/server/src/services/Accounting/Ledger.ts +++ b/server/src/services/Accounting/Ledger.ts @@ -1,9 +1,6 @@ import moment from 'moment'; import { defaultTo } from 'lodash'; -import { - ILedger, - ILedgerEntry -} from 'interfaces'; +import { IAccountTransaction, ILedger, ILedgerEntry } from 'interfaces'; import EntityRepository from 'repositories/EntityRepository'; export default class Ledger implements ILedger { @@ -11,7 +8,7 @@ export default class Ledger implements ILedger { /** * Constructor method. - * @param {ILedgerEntry[]} entries + * @param {ILedgerEntry[]} entries */ constructor(entries: ILedgerEntry[]) { this.entries = entries; @@ -20,26 +17,45 @@ export default class Ledger implements ILedger { /** * Filters the ledegr entries. * @param callback - * @returns + * @returns {ILedger} */ - filter(callback) { + public filter(callback): ILedger { const entries = this.entries.filter(callback); return new Ledger(entries); } - getEntries(): ILedgerEntry[] { + /** + * Retrieve the all entries of the ledger. + * @return {ILedgerEntry[]} + */ + public getEntries(): ILedgerEntry[] { return this.entries; } - whereContactId(contactId: number): ILedger { + /** + * Filters entries by th given contact id and returns a new ledger. + * @param {number} contactId + * @returns {ILedger} + */ + public whereContactId(contactId: number): ILedger { return this.filter((entry) => entry.contactId === contactId); } - whereAccountId(accountId: number): ILedger { + /** + * Filters entries by the given account id and returns a new ledger. + * @param {number} accountId + * @returns {ILedger} + */ + public whereAccountId(accountId: number): ILedger { return this.filter((entry) => entry.accountId === accountId); } - whereFromDate(fromDate: Date | string): ILedger { + /** + * Filters entries that before or same the given date and returns a new ledger. + * @param {Date|string} fromDate + * @returns {ILedger} + */ + public whereFromDate(fromDate: Date | string): ILedger { const fromDateParsed = moment(fromDate); return this.filter( @@ -48,7 +64,12 @@ export default class Ledger implements ILedger { ); } - whereToDate(toDate: Date | string): ILedger { + /** + * Filters ledger entries that after the given date and retruns a new ledger. + * @param {Date|string} toDate + * @returns {ILedger} + */ + public whereToDate(toDate: Date | string): ILedger { const toDateParsed = moment(toDate); return this.filter( @@ -59,15 +80,14 @@ export default class Ledger implements ILedger { /** * Retrieve the closing balance of the entries. - * @returns {number} + * @returns {number} */ - getClosingBalance() { + public getClosingBalance(): number { let closingBalance = 0; this.entries.forEach((entry) => { if (entry.accountNormal === 'credit') { closingBalance += entry.credit - entry.debit; - } else if (entry.accountNormal === 'debit') { closingBalance += entry.debit - entry.credit; } @@ -75,15 +95,25 @@ export default class Ledger implements ILedger { return closingBalance; } - static mappingTransactions(entries): ILedgerEntry[] { + /** + * Mappes the account transactions to ledger entries. + * @param {IAccountTransaction[]} entries + * @returns {ILedgerEntry[]} + */ + static mappingTransactions(entries: IAccountTransaction[]): ILedgerEntry[] { return entries.map(this.mapTransaction); } - - static mapTransaction(entry): ILedgerEntry { + + /** + * Mappes the account transaction to ledger entry. + * @param {IAccountTransaction} entry + * @returns {ILedgerEntry} + */ + static mapTransaction(entry: IAccountTransaction): ILedgerEntry { return { credit: defaultTo(entry.credit, 0), debit: defaultTo(entry.debit, 0), - accountNormal: entry.accountNormal, + accountNormal: entry.account.accountNormal, accountId: entry.accountId, contactId: entry.contactId, date: entry.date, @@ -91,10 +121,15 @@ export default class Ledger implements ILedger { transactionType: entry.referenceTypeFormatted, referenceNumber: entry.referenceNumber, referenceType: entry.referenceType, - } + }; } - static fromTransactions(transactions) { + /** + * Mappes the account transactions to ledger entries. + * @param {IAccountTransaction[]} transactions + * @returns {ILedger} + */ + static fromTransactions(transactions: IAccountTransaction[]): ILedger { const entries = Ledger.mappingTransactions(transactions); return new Ledger(entries); } diff --git a/server/src/services/Accounts/AccountsService.ts b/server/src/services/Accounts/AccountsService.ts index adbaaa90d..a7a0d6505 100644 --- a/server/src/services/Accounts/AccountsService.ts +++ b/server/src/services/Accounts/AccountsService.ts @@ -21,6 +21,7 @@ import events from 'subscribers/events'; import AccountTypesUtils from 'lib/AccountTypes'; import { ERRORS } from './constants'; import { flatToNestedArray } from 'utils'; +import I18nService from 'services/I18n/I18nService'; @Service() export default class AccountsService { @@ -36,6 +37,9 @@ export default class AccountsService { @EventDispatcher() eventDispatcher: EventDispatcherInterface; + @Inject() + i18nService: I18nService; + /** * Retrieve account type or throws service error. * @param {number} tenantId - @@ -721,7 +725,9 @@ export default class AccountsService { ...account.toJSON(), currencyCode: baseCurrency, })); - return flatToNestedArray(_accounts, { + return flatToNestedArray( + this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId), + { id: 'id', parentId: 'parent_account_id', }); diff --git a/server/src/services/Accounts/AccountsTypesServices.ts b/server/src/services/Accounts/AccountsTypesServices.ts index 3545d819a..f3e38bfb8 100644 --- a/server/src/services/Accounts/AccountsTypesServices.ts +++ b/server/src/services/Accounts/AccountsTypesServices.ts @@ -1,14 +1,21 @@ -import { Service } from 'typedi'; +import { Inject, Service } from 'typedi'; import { IAccountsTypesService, IAccountType } from 'interfaces'; import AccountTypesUtils from 'lib/AccountTypes'; +import I18nService from 'services/I18n/I18nService'; + @Service() export default class AccountsTypesService implements IAccountsTypesService { + @Inject() + i18nService: I18nService; + /** * Retrieve all accounts types. + * @param {number} tenantId - * @return {IAccountType} */ - getAccountsTypes(): IAccountType[] { - return AccountTypesUtils.getList(); + public getAccountsTypes(tenantId: number): IAccountType[] { + const accountTypes = AccountTypesUtils.getList(); + return this.i18nService.i18nMapper(accountTypes, ['label'], tenantId); } } diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts index c166e123a..0ec2b247a 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheet.ts @@ -16,13 +16,13 @@ import FinancialSheet from '../FinancialSheet'; export default class BalanceSheetStatement extends FinancialSheet { readonly query: IBalanceSheetQuery; + readonly numberFormat: INumberFormatQuery; readonly tenantId: number; readonly accounts: IAccount & { type: IAccountType }[]; readonly journalFinancial: IJournalPoster; readonly comparatorDateType: string; readonly dateRangeSet: string[]; readonly baseCurrency: string; - readonly numberFormat: INumberFormatQuery; /** * Constructor method. @@ -36,7 +36,8 @@ export default class BalanceSheetStatement extends FinancialSheet { query: IBalanceSheetQuery, accounts: IAccount & { type: IAccountType }[], journalFinancial: IJournalPoster, - baseCurrency: string + baseCurrency: string, + i18n ) { super(); @@ -46,10 +47,11 @@ export default class BalanceSheetStatement extends FinancialSheet { this.accounts = accounts; this.journalFinancial = journalFinancial; this.baseCurrency = baseCurrency; - this.comparatorDateType = query.displayColumnsType === 'total' ? 'day' : query.displayColumnsBy; + this.i18n = i18n; + this.initDateRangeCollection(); } @@ -256,7 +258,7 @@ export default class BalanceSheetStatement extends FinancialSheet { accounts: IAccount & { type: IAccountType }[] ): IBalanceSheetSection { const result = { - name: structure.name, + name: this.i18n.__(structure.name), sectionType: structure.sectionType, type: structure.type, ...(structure.type === 'accounts_section' diff --git a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts index 5fb30b244..3b98b4983 100644 --- a/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts +++ b/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetService.ts @@ -53,7 +53,7 @@ export default class BalanceSheetStatementService * @param {number} tenantId - * @returns {IBalanceSheetMeta} */ - reportMetadata(tenantId: number): IBalanceSheetMeta { + private reportMetadata(tenantId: number): IBalanceSheetMeta { const settings = this.tenancy.settings(tenantId); const isCostComputeRunning = this.inventoryService @@ -92,6 +92,8 @@ export default class BalanceSheetStatementService transactionsRepository, } = this.tenancy.repositories(tenantId); + const i18n = this.tenancy.i18n(tenantId); + // Settings tenant service. const settings = this.tenancy.settings(tenantId); const baseCurrency = settings.get({ @@ -113,7 +115,7 @@ export default class BalanceSheetStatementService // Retrieve all journal transactions based on the given query. const transactions = await transactionsRepository.journal({ fromDate: query.fromDate, - toDate: query.toDate, + toDate: query.toDate, }); // Transform transactions to journal collection. const transactionsJournal = Journal.fromTransactions( @@ -127,7 +129,8 @@ export default class BalanceSheetStatementService filter, accounts, transactionsJournal, - baseCurrency + baseCurrency, + i18n ); // Balance sheet data. const balanceSheetData = balanceSheetInstanace.reportData(); diff --git a/server/src/services/FinancialStatements/CashFlow/CashFlow.ts b/server/src/services/FinancialStatements/CashFlow/CashFlow.ts new file mode 100644 index 000000000..dd34d0c04 --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/CashFlow.ts @@ -0,0 +1,764 @@ +import * as R from 'ramda'; +import { defaultTo, map, set, sumBy, isEmpty, mapValues, get } from 'lodash'; +import * as mathjs from 'mathjs'; +import moment from 'moment'; +import { + IAccount, + ILedger, + INumberFormatQuery, + ICashFlowSchemaSection, + ICashFlowStatementQuery, + ICashFlowStatementNetIncomeSection, + ICashFlowStatementAccountSection, + ICashFlowSchemaSectionAccounts, + ICashFlowStatementAccountMeta, + ICashFlowSchemaAccountRelation, + ICashFlowStatementSectionType, + ICashFlowStatementData, + ICashFlowDatePeriod, + ICashFlowStatement, + ICashFlowSchemaTotalSection, + ICashFlowStatementTotalSection, + ICashFlowStatementSection, +} from 'interfaces'; +import CASH_FLOW_SCHEMA from './schema'; +import FinancialSheet from '../FinancialSheet'; +import { + transformToMapBy, + accumSum, + dateRangeFromToCollection, + applyMixins, +} from 'utils'; +import { + reduceDeep, + iteratee, + mapValuesDeep, + filterDeep, +} from 'utils/deepdash'; +import { ACCOUNT_ROOT_TYPE } from 'data/AccountTypes'; +import CashFlowDatePeriods from './CashFlowDatePeriods'; +import I18nService from 'services/I18n/I18nService'; + +const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; + +const DISPLAY_COLUMNS_BY = { + DATE_PERIODS: 'date_periods', + TOTAL: 'total', +}; + +class CashFlowStatement extends FinancialSheet implements ICashFlowStatement { + readonly baseCurrency: string; + readonly i18n: I18nService; + readonly sectionsByIds = {}; + readonly cashFlowSchemaMap: Map; + readonly cashFlowSchemaSeq: Array; + readonly accountByTypeMap: Map; + readonly accountsByRootType: Map; + readonly ledger: ILedger; + readonly cashLedger: ILedger; + readonly netIncomeLedger: ILedger; + readonly schemaSectionParserIteratee: any; + readonly query: ICashFlowStatementQuery; + readonly numberFormat: INumberFormatQuery; + readonly comparatorDateType: string; + readonly dateRangeSet: { fromDate: Date; toDate: Date }[]; + + /** + * Constructor method. + * @constructor + */ + constructor( + accounts: IAccount[], + ledger: ILedger, + cashLedger: ILedger, + netIncomeLedger: ILedger, + query: ICashFlowStatementQuery, + baseCurrency: string, + i18n + ) { + super(); + + this.baseCurrency = baseCurrency; + this.i18n = i18n; + this.ledger = ledger; + this.cashLedger = cashLedger; + this.netIncomeLedger = netIncomeLedger; + this.accountByTypeMap = transformToMapBy(accounts, 'accountType'); + this.accountsByRootType = transformToMapBy(accounts, 'accountRootType'); + this.schemaSectionParserIteratee = iteratee(this.schemaSectionParser); + this.query = query; + this.numberFormat = this.query.numberFormat; + this.dateRangeSet = []; + + this.comparatorDateType = query.displayColumnsType === 'total' + ? 'day' : query.displayColumnsBy; + + this.initDateRangeCollection(); + } + + // -------------------------------------------- + // # GENERAL UTILITIES + // -------------------------------------------- + /** + * Retrieve the expense accounts ids. + * @return {number[]} + */ + private getAccountsIdsByType(accountType: string): number[] { + const expenseAccounts = this.accountsByRootType.get(accountType); + const expenseAccountsIds = map(expenseAccounts, 'id'); + + return expenseAccountsIds; + } + + /** + * Detarmines the given display columns by type. + * @param {string} displayColumnsBy + * @returns {boolean} + */ + private isDisplayColumnsBy(displayColumnsBy: string): boolean { + return this.query.displayColumnsType === displayColumnsBy; + } + + /** + * Adjustments the given amount. + * @param {string} direction + * @param {number} amount - + * @return {number} + */ + private amountAdjustment(direction: 'mines' | 'plus', amount): number { + return R.when( + R.always(R.equals(direction, 'mines')), + R.multiply(-1) + )(amount); + } + + // -------------------------------------------- + // # NET INCOME NODE + // -------------------------------------------- + + /** + * Retrieve the accounts net income. + * @returns {number} - Amount of net income. + */ + private getAccountsNetIncome(): number { + // Mapping income/expense accounts ids. + const incomeAccountsIds = this.getAccountsIdsByType( + ACCOUNT_ROOT_TYPE.INCOME + ); + const expenseAccountsIds = this.getAccountsIdsByType( + ACCOUNT_ROOT_TYPE.EXPENSE + ); + + // Income closing balance. + const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => + this.netIncomeLedger.whereAccountId(id).getClosingBalance() + ); + // Expense closing balance. + const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => + this.netIncomeLedger.whereAccountId(id).getClosingBalance() + ); + // Net income = income - expenses. + const netIncome = incomeClosingBalance - expenseClosingBalance; + + return netIncome; + } + + /** + * Parses the net income section from the given section schema. + * @param {ICashFlowSchemaSection} sectionSchema - Report section schema. + * @returns {ICashFlowStatementNetIncomeSection} + */ + private netIncomeSectionMapper( + sectionSchema: ICashFlowSchemaSection + ): ICashFlowStatementNetIncomeSection { + const netIncome = this.getAccountsNetIncome(); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocPeriodsToNetIncomeNode.bind(this) + ) + )({ + id: sectionSchema.id, + label: this.i18n.__(sectionSchema.label), + total: this.getAmountMeta(netIncome), + sectionType: ICashFlowStatementSectionType.NET_INCOME, + }); + } + + // -------------------------------------------- + // # ACCOUNT NODE + // -------------------------------------------- + + /** + * Retrieve account meta. + * @param {ICashFlowSchemaAccountRelation} relation - Account relation. + * @param {IAccount} account - + * @returns {ICashFlowStatementAccountMeta} + */ + private accountMetaMapper( + relation: ICashFlowSchemaAccountRelation, + account: IAccount + ): ICashFlowStatementAccountMeta { + // Retrieve the closing balance of the given account. + const getClosingBalance = (id) => + this.ledger.whereAccountId(id).getClosingBalance(); + + const closingBalance = R.compose( + // Multiplies the amount by -1 in case the relation in mines. + R.curry(this.amountAdjustment)(relation.direction) + )(getClosingBalance(account.id)); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocPeriodsToAccountNode.bind(this) + ) + )({ + id: account.id, + code: account.code, + label: account.name, + accountType: account.accountType, + adjusmentType: relation.direction, + total: this.getAmountMeta(closingBalance), + sectionType: ICashFlowStatementSectionType.ACCOUNT, + }); + } + + /** + * Retrieve accounts sections by the given schema relation. + * @param {ICashFlowSchemaAccountRelation} relation + * @returns {ICashFlowStatementAccountMeta[]} + */ + private getAccountsBySchemaRelation( + relation: ICashFlowSchemaAccountRelation + ): ICashFlowStatementAccountMeta[] { + const accounts = defaultTo(this.accountByTypeMap.get(relation.type), []); + const accountMetaMapper = R.curry(this.accountMetaMapper.bind(this))( + relation + ); + return R.map(accountMetaMapper)(accounts); + } + + /** + * Retrieve the accounts meta. + * @param {string[]} types + * @returns {ICashFlowStatementAccountMeta[]} + */ + private getAccountsBySchemaRelations( + relations: ICashFlowSchemaAccountRelation[] + ): ICashFlowStatementAccountMeta[] { + return R.pipe( + R.append(R.map(this.getAccountsBySchemaRelation.bind(this))(relations)), + R.flatten + )([]); + } + + /** + * Calculates the accounts total + * @param {ICashFlowStatementAccountMeta[]} accounts + * @returns {number} + */ + private getAccountsMetaTotal( + accounts: ICashFlowStatementAccountMeta[] + ): number { + return sumBy(accounts, 'total.amount'); + } + + /** + * Retrieve the accounts section from the section schema. + * @param {ICashFlowSchemaSectionAccounts} sectionSchema + * @returns {ICashFlowStatementAccountSection} + */ + private accountsSectionParser( + sectionSchema: ICashFlowSchemaSectionAccounts + ): ICashFlowStatementAccountSection { + const { accountsRelations } = sectionSchema; + + const accounts = this.getAccountsBySchemaRelations(accountsRelations); + const total = this.getAccountsMetaTotal(accounts); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocPeriodsToAggregateNode.bind(this) + ) + )({ + sectionType: ICashFlowStatementSectionType.ACCOUNTS, + id: sectionSchema.id, + label: this.i18n.__(sectionSchema.label), + footerLabel: this.i18n.__(sectionSchema.footerLabel), + children: accounts, + total: this.getTotalAmountMeta(total), + }); + } + + /** + * Detarmines the schema section type. + * @param {string} type + * @param {ICashFlowSchemaSection} section + * @returns {boolean} + */ + private isSchemaSectionType( + type: string, + section: ICashFlowSchemaSection + ): boolean { + return type === section.sectionType; + } + + // -------------------------------------------- + // # AGGREGATE NODE + // -------------------------------------------- + + /** + * + * @param {ICashFlowSchemaSection} schemaSection + * @returns + */ + private regularSectionParser( + schemaSection: ICashFlowSchemaSection + ): ICashFlowStatementSection { + return { + id: schemaSection.id, + label: this.i18n.__(schemaSection.label), + footerLabel: this.i18n.__(schemaSection.footerLabel), + sectionType: ICashFlowStatementSectionType.REGULAR, + }; + } + + private transformSectionsToMap(sections: ICashFlowSchemaSection[]) { + return reduceDeep( + sections, + (acc, section) => { + if (section.id) { + acc[`${section.id}`] = section; + } + return acc; + }, + {}, + MAP_CONFIG + ); + } + + // -------------------------------------------- + // # TOTAL EQUATION NODE + // -------------------------------------------- + + private sectionsMapToTotal(mappedSections: { [key: number]: any }) { + return mapValues(mappedSections, (node) => get(node, 'total.amount') || 0); + } + + /** + * Evauluate equaation string with the given scope table. + * @param {string} equation - + * @param {{ [key: string]: number }} scope - + * @return {number} + */ + private evaluateEquation( + equation: string, + scope: { [key: string | number]: number } + ): number { + return mathjs.evaluate(equation, scope); + } + + /** + * Retrieve the total section from the eqauation parser. + * @param {ICashFlowSchemaTotalSection} sectionSchema + * @param {ICashFlowSchemaSection[]} accumlatedSections + */ + private totalEquationSectionParser( + accumlatedSections: ICashFlowSchemaSection[], + sectionSchema: ICashFlowSchemaTotalSection + ): ICashFlowStatementTotalSection { + const mappedSectionsById = this.transformSectionsToMap(accumlatedSections); + const nodesTotalById = this.sectionsMapToTotal(mappedSectionsById); + + const total = this.evaluateEquation(sectionSchema.equation, nodesTotalById); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + R.curry(this.assocTotalEquationDatePeriods.bind(this))( + mappedSectionsById, + sectionSchema.equation + ) + ) + )({ + sectionType: ICashFlowStatementSectionType.TOTAL, + id: sectionSchema.id, + label: this.i18n.__(sectionSchema.label), + total: this.getTotalAmountMeta(total), + }); + } + + /** + * Retrieve the beginning cash from date. + * @param {Date|string} fromDate - + * @return {Date} + */ + private beginningCashFrom(fromDate: string | Date): Date { + return moment(fromDate).subtract(1, 'days').toDate(); + } + + /** + * Retrieve account meta. + * @param {ICashFlowSchemaAccountRelation} relation + * @param {IAccount} account + * @returns {ICashFlowStatementAccountMeta} + */ + private cashAccountMetaMapper( + relation: ICashFlowSchemaAccountRelation, + account: IAccount + ): ICashFlowStatementAccountMeta { + const cashToDate = this.beginningCashFrom(this.query.fromDate); + + const closingBalance = this.cashLedger + .whereToDate(cashToDate) + .whereAccountId(account.id) + .getClosingBalance(); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocCashAtBeginningAccountDatePeriods.bind(this) + ) + )({ + id: account.id, + code: account.code, + label: account.name, + accountType: account.accountType, + adjusmentType: relation.direction, + total: this.getAmountMeta(closingBalance), + sectionType: ICashFlowStatementSectionType.ACCOUNT, + }); + } + + /** + * Retrieve accounts sections by the given schema relation. + * @param {ICashFlowSchemaAccountRelation} relation + * @returns {ICashFlowStatementAccountMeta[]} + */ + private getCashAccountsBySchemaRelation( + relation: ICashFlowSchemaAccountRelation + ): ICashFlowStatementAccountMeta[] { + const accounts = this.accountByTypeMap.get(relation.type) || []; + const accountMetaMapper = R.curry(this.cashAccountMetaMapper.bind(this))( + relation + ); + return accounts.map(accountMetaMapper); + } + + /** + * Retrieve the accounts meta. + * @param {string[]} types + * @returns {ICashFlowStatementAccountMeta[]} + */ + private getCashAccountsBySchemaRelations( + relations: ICashFlowSchemaAccountRelation[] + ): ICashFlowStatementAccountMeta[] { + return R.concat( + ...R.map(this.getCashAccountsBySchemaRelation.bind(this))(relations) + ); + } + + /** + * Parses the cash at beginning section. + * @param {ICashFlowSchemaTotalSection} sectionSchema - + * @return {} + */ + private cashAtBeginningSectionParser(sectionSchema) { + const { accountsRelations } = sectionSchema; + + const children = this.getCashAccountsBySchemaRelations(accountsRelations); + const total = this.getAccountsMetaTotal(children); + + return R.compose( + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocCashAtBeginningDatePeriods.bind(this) + ) + )({ + sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING, + id: sectionSchema.id, + label: this.i18n.__(sectionSchema.label), + children, + total: this.getTotalAmountMeta(total), + }); + } + + /** + * Parses the schema section. + * @param {ICashFlowSchemaSection} section + * @returns {ICashFlowSchemaSection} + */ + private schemaSectionParser( + section: ICashFlowSchemaSection, + key: number, + parentValue: ICashFlowSchemaSection[], + context, + accumlatedSections: ICashFlowSchemaSection[] + ): ICashFlowSchemaSection { + const isSchemaSectionType = R.curry(this.isSchemaSectionType); + + return R.compose( + // Accounts node. + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), + this.accountsSectionParser.bind(this) + ), + // Net income node. + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.NET_INCOME), + this.netIncomeSectionMapper.bind(this) + ), + // Cash at beginning node. + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.CASH_AT_BEGINNING), + R.curry(this.cashAtBeginningSectionParser.bind(this)) + ), + // Aggregate node. (that has no section type). + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.AGGREGATE), + this.regularSectionParser.bind(this) + ) + )(section); + } + + /** + * Parses the schema section. + * @param {ICashFlowSchemaSection} section + * @returns {ICashFlowSchemaSection} + */ + private schemaSectionTotalParser( + section: ICashFlowSchemaSection | ICashFlowStatementSection, + key: number, + parentValue: ICashFlowSchemaSection[], + context, + accumlatedSections: ICashFlowSchemaSection | ICashFlowStatementSection[] + ): ICashFlowSchemaSection { + const isSchemaSectionType = R.curry(this.isSchemaSectionType); + + return R.compose( + // Total equation section. + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.TOTAL), + R.curry(this.totalEquationSectionParser.bind(this))(accumlatedSections) + ) + )(section); + } + + /** + * + * @param acc + * @param value + * @param key + * @param parentValue + * @param context + * @returns + */ + private schemaSectionsReducer(acc, value, key, parentValue, context) { + set( + acc, + context.path, + this.schemaSectionParserIteratee(value, key, parentValue, context, acc) + ); + return acc; + } + + /** + * Schema sections parser. + * @param {ICashFlowSchemaSection[]}schema + * @returns + */ + private schemaSectionsParser(schema: ICashFlowSchemaSection[]) { + return reduceDeep( + schema, + this.schemaSectionsReducer.bind(this), + [], + MAP_CONFIG + ); + } + + /** + * Writes the `total` property to the aggregate node. + * @return {ICashFlowStatementSection} + */ + private assocRegularSectionTotal(section: ICashFlowStatementSection) { + const total = this.getAccountsMetaTotal(section.children); + return R.assoc('total', this.getTotalAmountMeta(total), section); + } + + /** + * Parses the given node on stage 2. + * @param {ICashFlowStatementSection} node + * @return {ICashFlowStatementSection} + */ + private sectionMapperAfterParsing(section: ICashFlowStatementSection) { + const isSchemaSectionType = R.curry(this.isSchemaSectionType); + + return R.compose( + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.REGULAR), + this.assocRegularSectionTotal.bind(this) + ), + R.when( + isSchemaSectionType(ICashFlowStatementSectionType.REGULAR), + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + this.assocPeriodsToAggregateNode.bind(this) + ) + ) + )(section); + } + + private regularSectionsTotal( + sections: ICashFlowSchemaSection[] + ): ICashFlowSchemaSection[] { + return mapValuesDeep( + sections, + this.sectionMapperAfterParsing.bind(this), + MAP_CONFIG + ); + } + + private totalSectionsParser( + sections: ICashFlowSchemaSection | ICashFlowStatementSection[] + ) { + return reduceDeep( + sections, + (acc, value, key, parentValue, context) => { + set( + acc, + context.path, + this.schemaSectionTotalParser(value, key, parentValue, context, acc) + ); + return acc; + }, + [], + MAP_CONFIG + ); + } + + // -------------------------------------------- + // REPORT FILTERING + // -------------------------------------------- + + /** + * Detarmines the given section has children and not empty. + * @param {ICashFlowStatementSection} section + * @returns {boolean} + */ + private isSectionHasChildren(section: ICashFlowStatementSection): boolean { + return !isEmpty(section.children); + } + + /** + * Detarmines whether the section has no zero amount. + * @param {ICashFlowStatementSection} section + * @returns {boolean} + */ + private isSectionNoneZero(section: ICashFlowStatementSection): boolean { + return section.total.amount !== 0; + } + + /** + * Detarmines whether the parent accounts sections has children. + * @param {ICashFlowStatementSection} section + * @returns {boolean} + */ + private isAccountsSectionHasChildren( + section: ICashFlowStatementSection[] + ): boolean { + const isSchemaSectionType = R.curry(this.isSchemaSectionType); + + return R.ifElse( + isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNTS), + this.isSectionHasChildren.bind(this), + R.always(true) + )(section); + } + + /** + * Detarmines the account section has no zero otherwise returns true. + * @param {ICashFlowStatementSection} section + * @returns {boolean} + */ + private isAccountLeafNoneZero(section: ICashFlowStatementSection[]): boolean { + const isSchemaSectionType = R.curry(this.isSchemaSectionType); + + return R.ifElse( + isSchemaSectionType(ICashFlowStatementSectionType.ACCOUNT), + this.isSectionNoneZero.bind(this), + R.always(true) + )(section); + } + + /** + * Deep filters the non-zero accounts leafs of the report sections. + * @param {ICashFlowStatementSection[]} sections + * @returns {ICashFlowStatementSection[]} + */ + private filterNoneZeroAccountsLeafs(sections: ICashFlowStatementSection[]) { + return filterDeep( + sections, + this.isAccountLeafNoneZero.bind(this), + MAP_CONFIG + ) || []; + } + + /** + * Deep filter the non-children sections of the report sections. + * @param {ICashFlowStatementSection[]} sections + * @returns {ICashFlowStatementSection[]} + */ + private filterNoneChildrenSections(sections: ICashFlowStatementSection[]) { + return filterDeep( + sections, + this.isAccountsSectionHasChildren.bind(this), + MAP_CONFIG + ) || []; + } + + /** + * Filters the report data. + * @param {ICashFlowStatementSection[]} sections + * @returns {ICashFlowStatementSection[]} + */ + private filterReportData(sections: ICashFlowStatementSection[]) { + return R.compose( + this.filterNoneChildrenSections.bind(this), + this.filterNoneZeroAccountsLeafs.bind(this) + )(sections); + } + + /** + * Schema parser. + * @param {ICashFlowSchemaSection[]} schema + * @returns {ICashFlowSchemaSection[]} + */ + private schemaParser( + schema: ICashFlowSchemaSection[] + ): ICashFlowSchemaSection[] { + return R.compose( + R.when( + R.always(!this.query.noneTransactions && !this.query.noneZero), + this.filterReportData.bind(this) + ), + this.totalSectionsParser.bind(this), + this.regularSectionsTotal.bind(this), + this.schemaSectionsParser.bind(this) + )(schema); + } + + /** + * Retrieve the cashflow statement data. + * @return {ICashFlowStatementData} + */ + public reportData(): ICashFlowStatementData { + return this.schemaParser(R.clone(CASH_FLOW_SCHEMA)); + } +} + +applyMixins(CashFlowStatement, [CashFlowDatePeriods]); + +export default CashFlowStatement; diff --git a/server/src/services/FinancialStatements/CashFlow/CashFlowDatePeriods.ts b/server/src/services/FinancialStatements/CashFlow/CashFlowDatePeriods.ts new file mode 100644 index 000000000..75465b74b --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/CashFlowDatePeriods.ts @@ -0,0 +1,410 @@ +import * as R from 'ramda'; +import { sumBy, mapValues, get } from 'lodash'; +import { ACCOUNT_ROOT_TYPE } from 'data/AccountTypes'; +import { accumSum, dateRangeFromToCollection } from 'utils'; +import { + ICashFlowDatePeriod, + ICashFlowStatementNetIncomeSection, + ICashFlowStatementAccountSection, + ICashFlowStatementSection, + ICashFlowSchemaTotalSection, + IFormatNumberSettings, + ICashFlowStatementTotalSection, + IDateRange, + ICashFlowStatementQuery +} from 'interfaces'; + +export default class CashFlowStatementDatePeriods { + dateRangeSet: IDateRange[]; + query: ICashFlowStatementQuery; + + /** + * Initialize date range set. + */ + private initDateRangeCollection() { + this.dateRangeSet = dateRangeFromToCollection( + this.query.fromDate, + this.query.toDate, + this.comparatorDateType + ); + } + + /** + * Retrieve the date period meta. + * @param {number} total - Total amount. + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {ICashFlowDatePeriod} + */ + private getDatePeriodTotalMeta( + total: number, + fromDate: Date, + toDate: Date, + overrideSettings: IFormatNumberSettings = {} + ): ICashFlowDatePeriod { + return this.getDatePeriodMeta(total, fromDate, toDate, { + money: true, + ...overrideSettings, + }); + } + + /** + * Retrieve the date period meta. + * @param {number} total - Total amount. + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {ICashFlowDatePeriod} + */ + private getDatePeriodMeta( + total: number, + fromDate: Date, + toDate: Date, + overrideSettings?: IFormatNumberSettings + ): ICashFlowDatePeriod { + return { + fromDate: this.getDateMeta(fromDate), + toDate: this.getDateMeta(toDate), + total: this.getAmountMeta(total, overrideSettings), + }; + } + + // Net income -------------------- + + /** + * Retrieve the net income between the given date range. + * @param {Date} fromDate + * @param {Date} toDate + * @returns {number} + */ + private getNetIncomeDateRange(fromDate: Date, toDate: Date) { + // Mapping income/expense accounts ids. + const incomeAccountsIds = this.getAccountsIdsByType( + ACCOUNT_ROOT_TYPE.INCOME + ); + const expenseAccountsIds = this.getAccountsIdsByType( + ACCOUNT_ROOT_TYPE.EXPENSE + ); + + // Income closing balance. + const incomeClosingBalance = accumSum(incomeAccountsIds, (id) => + this.netIncomeLedger + .whereFromDate(fromDate) + .whereToDate(toDate) + .whereAccountId(id) + .getClosingBalance() + ); + // Expense closing balance. + const expenseClosingBalance = accumSum(expenseAccountsIds, (id) => + this.netIncomeLedger + .whereToDate(toDate) + .whereFromDate(fromDate) + .whereAccountId(id) + .getClosingBalance() + ); + // Net income = income - expenses. + const netIncome = incomeClosingBalance - expenseClosingBalance; + + return netIncome; + } + + /** + * Retrieve the net income of date period. + * @param {IDateRange} dateRange - + * @retrun {ICashFlowDatePeriod} + */ + private getNetIncomeDatePeriod(dateRange): ICashFlowDatePeriod { + const total = this.getNetIncomeDateRange( + dateRange.fromDate, + dateRange.toDate + ); + return this.getDatePeriodMeta(total, dateRange.fromDate, dateRange.toDate); + } + + /** + * Retrieve the net income node between the given date ranges. + * @param {Date} fromDate + * @param {Date} toDate + * @returns {ICashFlowDatePeriod[]} + */ + private getNetIncomeDatePeriods( + section: ICashFlowStatementNetIncomeSection + ): ICashFlowDatePeriod[] { + return this.dateRangeSet.map(this.getNetIncomeDatePeriod.bind(this)); + } + + /** + * Writes periods property to net income section. + * @param {ICashFlowStatementNetIncomeSection} section + * @returns {ICashFlowStatementNetIncomeSection} + */ + private assocPeriodsToNetIncomeNode( + section: ICashFlowStatementNetIncomeSection + ): ICashFlowStatementNetIncomeSection { + const incomeDatePeriods = this.getNetIncomeDatePeriods(section); + return R.assoc('periods', incomeDatePeriods, section); + } + + // Account nodes -------------------- + + /** + * Retrieve the account total between date range. + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {number} + */ + private getAccountTotalDateRange( + node: ICashFlowStatementAccountSection, + fromDate: Date, + toDate: Date + ): number { + const closingBalance = this.ledger + .whereFromDate(fromDate) + .whereToDate(toDate) + .whereAccountId(node.id) + .getClosingBalance(); + + return this.amountAdjustment(node.adjusmentType, closingBalance); + } + + /** + * Retrieve the given account node total date period. + * @param {ICashFlowStatementAccountSection} node - + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {ICashFlowDatePeriod} + */ + private getAccountTotalDatePeriod( + node: ICashFlowStatementAccountSection, + fromDate: Date, + toDate: Date + ): ICashFlowDatePeriod { + const total = this.getAccountTotalDateRange(node, fromDate, toDate); + return this.getDatePeriodMeta(total, fromDate, toDate); + } + + /** + * Retrieve the accounts date periods nodes of the give account node. + * @param {ICashFlowStatementAccountSection} node - + * @return {ICashFlowDatePeriod[]} + */ + private getAccountDatePeriods( + node: ICashFlowStatementAccountSection + ): ICashFlowDatePeriod[] { + return this.getNodeDatePeriods( + node, + this.getAccountTotalDatePeriod.bind(this) + ); + } + + /** + * Writes `periods` property to account node. + * @param {ICashFlowStatementAccountSection} node - + * @return {ICashFlowStatementAccountSection} + */ + private assocPeriodsToAccountNode( + node: ICashFlowStatementAccountSection + ): ICashFlowStatementAccountSection { + const datePeriods = this.getAccountDatePeriods(node); + return R.assoc('periods', datePeriods, node); + } + + // Aggregate node ------------------------- + + /** + * Retrieve total of the given period index for node that has children nodes. + * @return {number} + */ + private getChildrenTotalPeriodByIndex( + node: ICashFlowStatementSection, + index: number + ): number { + return sumBy(node.children, `periods[${index}].total.amount`); + } + + /** + * Retrieve date period meta of the given node index. + * @param {ICashFlowStatementSection} node - + * @param {number} index - Loop index. + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + */ + private getChildrenTotalPeriodMetaByIndex( + node: ICashFlowStatementSection, + index: number, + fromDate: Date, + toDate: Date + ) { + const total = this.getChildrenTotalPeriodByIndex(node, index); + return this.getDatePeriodTotalMeta(total, fromDate, toDate); + } + + /** + * Retrieve the date periods of aggregate node. + * @param {ICashFlowStatementSection} node + */ + private getAggregateNodeDatePeriods(node: ICashFlowStatementSection) { + const getChildrenTotalPeriodMetaByIndex = R.curry( + this.getChildrenTotalPeriodMetaByIndex.bind(this) + )(node); + + return this.dateRangeSet.map((dateRange, index) => + getChildrenTotalPeriodMetaByIndex( + index, + dateRange.fromDate, + dateRange.toDate + ) + ); + } + + /** + * Writes `periods` property to aggregate section node. + * @param {ICashFlowStatementSection} node - + * @return {ICashFlowStatementSection} + */ + private assocPeriodsToAggregateNode( + node: ICashFlowStatementSection + ): ICashFlowStatementSection { + const datePeriods = this.getAggregateNodeDatePeriods(node); + return R.assoc('periods', datePeriods, node); + } + + // Total equation node -------------------- + + private sectionsMapToTotalPeriod( + mappedSections: { [key: number]: any }, + index + ) { + return mapValues( + mappedSections, + (node) => get(node, `periods[${index}].total.amount`) || 0 + ); + } + + /** + * Retrieve the date periods of the given total equation. + * @param {} + * @param {string} equation - + * @return {ICashFlowDatePeriod[]} + */ + private getTotalEquationDatePeriods( + node: ICashFlowSchemaTotalSection, + equation: string, + nodesTable + ): ICashFlowDatePeriod[] { + return this.getNodeDatePeriods(node, (node, fromDate, toDate, index) => { + const periodScope = this.sectionsMapToTotalPeriod(nodesTable, index); + const total = this.evaluateEquation(equation, periodScope); + + return this.getDatePeriodTotalMeta(total, fromDate, toDate); + }); + } + + /** + * Associates the total periods of total equation to the ginve total node.. + * @param {ICashFlowSchemaTotalSection} totalSection - + * @return {ICashFlowStatementTotalSection} + */ + private assocTotalEquationDatePeriods( + nodesTable: any, + equation: string, + node: ICashFlowSchemaTotalSection + ): ICashFlowStatementTotalSection { + const datePeriods = this.getTotalEquationDatePeriods( + node, + equation, + nodesTable + ); + + return R.assoc('periods', datePeriods, node); + } + + // Cash at beginning ---------------------- + + /** + * Retrieve the date preioods of the given node and accumlated function. + * @param {} node + * @param {} + * @return {} + */ + private getNodeDatePeriods(node, callback) { + const curriedCallback = R.curry(callback)(node); + + return this.dateRangeSet.map((dateRange, index) => { + return curriedCallback(dateRange.fromDate, dateRange.toDate, index); + }); + } + + /** + * Retrieve the account total between date range. + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {number} + */ + private getBeginningCashAccountDateRange( + node: ICashFlowStatementSection, + fromDate: Date, + toDate: Date + ) { + const cashToDate = this.beginningCashFrom(fromDate); + + return this.cashLedger + .whereToDate(cashToDate) + .whereAccountId(node.id) + .getClosingBalance(); + } + + /** + * Retrieve the beginning cash date period. + * @param {ICashFlowStatementSection} node - + * @param {Date} fromDate - From date. + * @param {Date} toDate - To date. + * @return {ICashFlowDatePeriod} + */ + private getBeginningCashDatePeriod( + node: ICashFlowStatementSection, + fromDate: Date, + toDate: Date + ) { + const total = this.getBeginningCashAccountDateRange(node, fromDate, toDate); + + return this.getDatePeriodTotalMeta(total, fromDate, toDate); + } + + /** + * Retrieve the beginning cash account periods. + * @param {ICashFlowStatementSection} node + * @return {ICashFlowDatePeriod} + */ + private getBeginningCashAccountPeriods( + node: ICashFlowStatementSection + ): ICashFlowDatePeriod { + return this.getNodeDatePeriods( + node, + this.getBeginningCashDatePeriod.bind(this) + ); + } + + /** + * Writes `periods` property to cash at beginning date periods. + * @param {ICashFlowStatementSection} section - + * @return {ICashFlowStatementSection} + */ + private assocCashAtBeginningDatePeriods( + node: ICashFlowStatementSection + ): ICashFlowStatementSection { + const datePeriods = this.getAggregateNodeDatePeriods(node); + return R.assoc('periods', datePeriods, node); + } + + /** + * Associates `periods` propery to cash at beginning account node. + * @param {ICashFlowStatementSection} node - + * @return {ICashFlowStatementSection} + */ + private assocCashAtBeginningAccountDatePeriods( + node: ICashFlowStatementSection + ): ICashFlowStatementSection { + const datePeriods = this.getBeginningCashAccountPeriods(node); + return R.assoc('periods', datePeriods, node); + } +} diff --git a/server/src/services/FinancialStatements/CashFlow/CashFlowRepository.ts b/server/src/services/FinancialStatements/CashFlow/CashFlowRepository.ts new file mode 100644 index 000000000..1bb2431d2 --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/CashFlowRepository.ts @@ -0,0 +1,149 @@ +import { Inject, Service } from 'typedi'; +import moment from 'moment'; +import { + ICashFlowStatementQuery, + IAccountTransaction, + IAccount, +} from 'interfaces'; +import HasTenancyService from 'services/Tenancy/TenancyService'; + +@Service() +export default class CashFlowRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the group type from periods type. + * @param {string} displayType + * @returns {string} + */ + protected getGroupTypeFromPeriodsType(displayType: string) { + const displayTypes = { + year: 'year', + day: 'day', + month: 'month', + quarter: 'month', + week: 'day', + }; + return displayTypes[displayType] || 'month'; + } + + /** + * Retrieve the cashflow accounts. + * @returns {Promise} + */ + public async cashFlowAccounts(tenantId: number): Promise { + const { Account } = this.tenancy.models(tenantId); + + const accounts = await Account.query(); + + return accounts; + } + + /** + * Retrieve total of csah at beginning transactions. + * @param {number} tenantId - + * @param {ICashFlowStatementQuery} filter - + * @return {Promise} + */ + public cashAtBeginningTotalTransactions( + tenantId: number, + filter: ICashFlowStatementQuery + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + const cashBeginningPeriod = moment(filter.fromDate) + .subtract(1, 'day') + .toDate(); + + return AccountTransaction.query().onBuild((query) => { + query.modify('creditDebitSummation'); + + query.select('accountId'); + query.groupBy('accountId'); + + query.withGraphFetched('account'); + query.modify('filterDateRange', null, cashBeginningPeriod); + }); + } + + /** + * Retrieve accounts transactions. + * @param {number} tenantId - + * @param {ICashFlowStatementQuery} filter + * @return {Promise} + */ + public getAccountsTransactions( + tenantId: number, + filter: ICashFlowStatementQuery + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + const groupByDateType = this.getGroupTypeFromPeriodsType( + filter.displayColumnsBy + ); + + return AccountTransaction.query().onBuild((query) => { + query.modify('creditDebitSummation'); + query.modify('groupByDateFormat', groupByDateType); + + query.select('accountId'); + + query.groupBy('accountId'); + query.withGraphFetched('account'); + query.modify('filterDateRange', filter.fromDate, filter.toDate); + }); + } + + /** + * Retrieve the net income tranasctions. + * @param {number} tenantId - + * @param {ICashFlowStatementQuery} query - + * @return {Promise} + */ + public getNetIncomeTransactions( + tenantId: number, + filter: ICashFlowStatementQuery + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + const groupByDateType = this.getGroupTypeFromPeriodsType( + filter.displayColumnsBy + ); + + return AccountTransaction.query().onBuild((query) => { + query.modify('creditDebitSummation'); + query.modify('groupByDateFormat', groupByDateType); + + query.select('accountId'); + query.groupBy('accountId'); + + query.withGraphFetched('account'); + query.modify('filterDateRange', filter.fromDate, filter.toDate); + }); + } + + /** + * Retrieve peridos of cash at beginning transactions. + * @param {number} tenantId - + * @param {ICashFlowStatementQuery} filter - + * @return {Promise} + */ + public cashAtBeginningPeriodTransactions( + tenantId: number, + filter: ICashFlowStatementQuery + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + const groupByDateType = this.getGroupTypeFromPeriodsType( + filter.displayColumnsBy + ); + + return AccountTransaction.query().onBuild((query) => { + query.modify('creditDebitSummation'); + query.modify('groupByDateFormat', groupByDateType); + + query.select('accountId'); + query.groupBy('accountId'); + + query.withGraphFetched('account'); + query.modify('filterDateRange', filter.fromDate, filter.toDate); + }); + } +} diff --git a/server/src/services/FinancialStatements/CashFlow/CashFlowService.ts b/server/src/services/FinancialStatements/CashFlow/CashFlowService.ts new file mode 100644 index 000000000..378182613 --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/CashFlowService.ts @@ -0,0 +1,177 @@ +import moment from 'moment'; +import { Service, Inject } from 'typedi'; +import * as R from 'ramda'; +import TenancyService from 'services/Tenancy/TenancyService'; +import FinancialSheet from '../FinancialSheet'; +import { + ICashFlowStatementService, + ICashFlowStatementQuery, + ICashFlowStatementDOO, + IAccountTransaction, + ICashFlowStatementMeta +} from 'interfaces'; +import CashFlowStatement from './CashFlow'; +import Ledger from 'services/Accounting/Ledger'; +import CashFlowRepository from './CashFlowRepository'; +import InventoryService from 'services/Inventory/Inventory'; +import { parseBoolean } from 'utils'; + +@Service() +export default class CashFlowStatementService + extends FinancialSheet + implements ICashFlowStatementService +{ + @Inject() + tenancy: TenancyService; + + @Inject() + cashFlowRepo: CashFlowRepository; + + @Inject() + inventoryService: InventoryService; + + /** + * Defaults balance sheet filter query. + * @return {IBalanceSheetQuery} + */ + get defaultQuery(): ICashFlowStatementQuery { + return { + displayColumnsType: 'total', + displayColumnsBy: 'day', + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().endOf('year').format('YYYY-MM-DD'), + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + noneZero: false, + noneTransactions: false, + basis: 'cash', + }; + } + + /** + * Retrieve cash at beginning transactions. + * @param {number} tenantId - + * @param {ICashFlowStatementQuery} filter - + * @retrun {Promise} + */ + private async cashAtBeginningTransactions( + tenantId: number, + filter: ICashFlowStatementQuery + ): Promise { + const appendPeriodsOperToChain = (trans) => + R.append( + this.cashFlowRepo.cashAtBeginningPeriodTransactions(tenantId, filter), + trans + ); + + const promisesChain = R.pipe( + R.append( + this.cashFlowRepo.cashAtBeginningTotalTransactions(tenantId, filter) + ), + R.when( + R.always(R.equals(filter.displayColumnsType, 'date_periods')), + appendPeriodsOperToChain + ) + )([]); + const promisesResults = await Promise.all(promisesChain); + const transactions = R.flatten(promisesResults); + + return transactions; + } + + /** + * Retrieve the cash flow sheet statement. + * @param {number} tenantId + * @param {ICashFlowStatementQuery} query + * @returns {Promise} + */ + public async cashFlow( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise { + const i18n = this.tenancy.i18n(tenantId); + + // Retrieve all accounts on the storage. + const accounts = await this.cashFlowRepo.cashFlowAccounts(tenantId); + + // Settings tenant service. + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + const filter = { + ...this.defaultQuery, + ...query, + }; + // Retrieve the accounts transactions. + const transactions = await this.cashFlowRepo.getAccountsTransactions( + tenantId, + filter + ); + // Retrieve the net income transactions. + const netIncome = await this.cashFlowRepo.getNetIncomeTransactions( + tenantId, + filter + ); + // Retrieve the cash at beginning transactions. + const cashAtBeginningTransactions = await this.cashAtBeginningTransactions( + tenantId, + filter + ); + // Transformes the transactions to ledgers. + const ledger = Ledger.fromTransactions(transactions); + const cashLedger = Ledger.fromTransactions(cashAtBeginningTransactions); + const netIncomeLedger = Ledger.fromTransactions(netIncome); + + // Cash flow statement. + const cashFlowInstance = new CashFlowStatement( + accounts, + ledger, + cashLedger, + netIncomeLedger, + filter, + baseCurrency, + i18n + ); + + return { + data: cashFlowInstance.reportData(), + query: filter, + meta: this.reportMetadata(tenantId), + }; + } + + /** + * Retrieve the balance sheet meta. + * @param {number} tenantId - + * @returns {ICashFlowStatementMeta} + */ + private reportMetadata(tenantId: number): ICashFlowStatementMeta { + const settings = this.tenancy.settings(tenantId); + + const isCostComputeRunning = this.inventoryService + .isItemsCostComputeRunning(tenantId); + + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + return { + isCostComputeRunning: parseBoolean(isCostComputeRunning, false), + organizationName, + baseCurrency + }; + } +} diff --git a/server/src/services/FinancialStatements/CashFlow/CashFlowTable.ts b/server/src/services/FinancialStatements/CashFlow/CashFlowTable.ts new file mode 100644 index 000000000..3c9a60ae9 --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/CashFlowTable.ts @@ -0,0 +1,365 @@ +import * as R from 'ramda'; +import { isEmpty, times } from 'lodash'; +import moment from 'moment'; +import { + ICashFlowStatementSection, + ICashFlowStatementSectionType, + ICashFlowStatement, + ITableRow, + ITableColumn, + ICashFlowStatementQuery, + IDateRange, + ICashFlowStatementDOO, +} from 'interfaces'; +import { dateRangeFromToCollection, tableRowMapper } from 'utils'; +import { mapValuesDeep } from 'utils/deepdash'; + +enum IROW_TYPE { + AGGREGATE = 'AGGREGATE', + NET_INCOME = 'NET_INCOME', + ACCOUNTS = 'ACCOUNTS', + ACCOUNT = 'ACCOUNT', + TOTAL = 'TOTAL', +} +const DEEP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; +const DISPLAY_COLUMNS_BY = { + DATE_PERIODS: 'date_periods', + TOTAL: 'total', +}; + +export default class CashFlowTable implements ICashFlowTable { + private report: ICashFlowStatementDOO; + private i18n; + private dateRangeSet: IDateRange[]; + + /** + * Constructor method. + * @param {ICashFlowStatement} reportStatement + */ + constructor(reportStatement: ICashFlowStatementDOO, i18n) { + this.report = reportStatement; + this.i18n = i18n; + this.dateRangeSet = []; + this.initDateRangeCollection(); + } + + /** + * Initialize date range set. + */ + private initDateRangeCollection() { + this.dateRangeSet = dateRangeFromToCollection( + this.report.query.fromDate, + this.report.query.toDate, + this.report.query.displayColumnsBy + ); + } + + /** + * Retrieve the date periods columns accessors. + */ + private datePeriodsColumnsAccessors() { + return this.dateRangeSet.map((dateRange: IDateRange, index) => ({ + key: `date-range-${index}`, + accessor: `periods[${index}].total.formattedAmount`, + })); + } + + /** + * Retrieve the total column accessor. + */ + private totalColumnAccessor() { + return [{ key: 'total', accessor: 'total.formattedAmount' }]; + } + + /** + * Retrieve the common columns for all report nodes. + */ + private commonColumns() { + return R.compose( + R.concat([{ key: 'label', accessor: 'label' }]), + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + R.concat(this.datePeriodsColumnsAccessors()) + ), + R.concat(this.totalColumnAccessor()) + )([]); + } + + /** + * Retrieve the table rows of regular section. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow[]} + */ + private regularSectionMapper(section: ICashFlowStatementSection): ITableRow { + const columns = this.commonColumns(); + + return tableRowMapper(section, columns, { + rowTypes: [IROW_TYPE.AGGREGATE], + id: section.id, + }); + } + + /** + * Retrieve the net income table rows of the section. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow} + */ + private netIncomeSectionMapper( + section: ICashFlowStatementSection + ): ITableRow { + const columns = this.commonColumns(); + + return tableRowMapper(section, columns, { + rowTypes: [IROW_TYPE.NET_INCOME, IROW_TYPE.TOTAL], + id: section.id, + }); + } + + /** + * Retrieve the accounts table rows of the section. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow} + */ + private accountsSectionMapper(section: ICashFlowStatementSection): ITableRow { + const columns = this.commonColumns(); + + return tableRowMapper(section, columns, { + rowTypes: [IROW_TYPE.ACCOUNTS], + id: section.id, + }); + } + + /** + * Retrieve the account table row of account section. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow} + */ + private accountSectionMapper(section: ICashFlowStatementSection): ITableRow { + const columns = this.commonColumns(); + + return tableRowMapper(section, columns, { + rowTypes: [IROW_TYPE.ACCOUNT], + id: `account-${section.id}`, + }); + } + + /** + * Retrieve the total table rows from the given total section. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow} + */ + private totalSectionMapper(section: ICashFlowStatementSection): ITableRow { + const columns = this.commonColumns(); + + return tableRowMapper(section, columns, { + rowTypes: [IROW_TYPE.TOTAL], + id: section.id, + }); + } + + /** + * Detarmines the schema section type. + * @param {string} type + * @param {ICashFlowSchemaSection} section + * @returns {boolean} + */ + private isSectionHasType( + type: string, + section: ICashFlowStatementSection + ): boolean { + return type === section.sectionType; + } + + /** + * The report section mapper. + * @param {ICashFlowStatementSection} section + * @returns {ITableRow} + */ + private sectionMapper( + section: ICashFlowStatementSection, + key: string, + parentSection: ICashFlowStatementSection + ): ITableRow { + const isSectionHasType = R.curry(this.isSectionHasType); + + return R.pipe( + R.when( + isSectionHasType(ICashFlowStatementSectionType.REGULAR), + this.regularSectionMapper.bind(this) + ), + R.when( + isSectionHasType(ICashFlowStatementSectionType.CASH_AT_BEGINNING), + this.regularSectionMapper.bind(this) + ), + R.when( + isSectionHasType(ICashFlowStatementSectionType.NET_INCOME), + this.netIncomeSectionMapper.bind(this) + ), + R.when( + isSectionHasType(ICashFlowStatementSectionType.ACCOUNTS), + this.accountsSectionMapper.bind(this) + ), + R.when( + isSectionHasType(ICashFlowStatementSectionType.ACCOUNT), + this.accountSectionMapper.bind(this) + ), + R.when( + isSectionHasType(ICashFlowStatementSectionType.TOTAL), + this.totalSectionMapper.bind(this) + ) + )(section); + } + + /** + * Mappes the sections to the table rows. + * @param {ICashFlowStatementSection[]} sections + * @returns {ITableRow[]} + */ + private mapSectionsToTableRows( + sections: ICashFlowStatementSection[] + ): ITableRow[] { + return mapValuesDeep(sections, this.sectionMapper.bind(this), DEEP_CONFIG); + } + + /** + * Appends the total to section's children. + * @param {ICashFlowStatementSection} section + * @returns {ICashFlowStatementSection} + */ + private appendTotalToSectionChildren( + section: ICashFlowStatementSection + ): ICashFlowStatementSection { + const label = section.footerLabel + ? section.footerLabel + : this.i18n.__('Total {{accountName}}', { accountName: section.label }); + + section.children.push({ + sectionType: ICashFlowStatementSectionType.TOTAL, + label, + periods: section.periods, + total: section.total, + }); + return section; + } + + /** + * + * @param {ICashFlowStatementSection} section + * @returns {ICashFlowStatementSection} + */ + private mapSectionsToAppendTotalChildren( + section: ICashFlowStatementSection + ): ICashFlowStatementSection { + const isSectionHasChildren = (section) => !isEmpty(section.children); + + return R.compose( + R.when(isSectionHasChildren, this.appendTotalToSectionChildren.bind(this)) + )(section); + } + + /** + * Appends total node to children section. + * @param {ICashFlowStatementSection[]} sections + * @returns {ICashFlowStatementSection[]} + */ + private appendTotalToChildren(sections: ICashFlowStatementSection[]) { + return mapValuesDeep( + sections, + this.mapSectionsToAppendTotalChildren.bind(this), + DEEP_CONFIG + ); + } + + /** + * Retrieve the table rows of cash flow statement. + * @param {ICashFlowStatementSection[]} sections + * @returns {ITableRow[]} + */ + public tableRows(): ITableRow[] { + const sections = this.report.data; + + return R.pipe( + this.appendTotalToChildren.bind(this), + this.mapSectionsToTableRows.bind(this) + )(sections); + } + + /** + * Retrieve the total columns. + * @returns {ITableColumn} + */ + private totalColumns(): ITableColumn[] { + return [{ key: 'total', label: this.i18n.__('Total') }]; + } + + /** + * Retrieve the formatted column label from the given date range. + * @param {ICashFlowDateRange} dateRange - + * @return {string} + */ + private formatColumnLabel(dateRange: ICashFlowDateRange) { + const monthFormat = (range) => moment(range.toDate).format('YYYY-MM'); + const yearFormat = (range) => moment(range.toDate).format('YYYY'); + const dayFormat = (range) => moment(range.toDate).format('YYYY-MM-DD'); + + const conditions = [ + ['month', monthFormat], + ['year', yearFormat], + ['day', dayFormat], + ['quarter', monthFormat], + ['week', dayFormat], + ]; + const conditionsPairs = R.map( + ([type, formatFn]) => [ + R.always(this.isDisplayColumnsType(type)), + formatFn, + ], + conditions + ); + + return R.compose(R.cond(conditionsPairs))(dateRange); + } + + /** + * Date periods columns. + * @returns {ITableColumn[]} + */ + private datePeriodsColumns(): ITableColumn[] { + return this.dateRangeSet.map((dateRange, index) => ({ + key: `date-range-${index}`, + label: this.formatColumnLabel(dateRange), + })); + } + + /** + * Detarmines the given column type is the current. + * @reutrns {boolean} + */ + private isDisplayColumnsBy(displayColumnsType: string): Boolean { + return this.report.query.displayColumnsType === displayColumnsType; + } + + /** + * Detarmines whether the given display columns type is the current. + * @param {string} displayColumnsBy + * @returns {boolean} + */ + private isDisplayColumnsType(displayColumnsBy: string): Boolean { + return this.report.query.displayColumnsBy === displayColumnsBy; + } + + /** + * Retrieve the table columns. + * @return {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + return R.compose( + R.concat([{ key: 'name', label: this.i18n.__('Account name') }]), + R.when( + R.always(this.isDisplayColumnsBy(DISPLAY_COLUMNS_BY.DATE_PERIODS)), + R.concat(this.datePeriodsColumns()) + ), + R.concat(this.totalColumns()) + )([]); + } +} diff --git a/server/src/services/FinancialStatements/CashFlow/schema.ts b/server/src/services/FinancialStatements/CashFlow/schema.ts new file mode 100644 index 000000000..ce786c2e9 --- /dev/null +++ b/server/src/services/FinancialStatements/CashFlow/schema.ts @@ -0,0 +1,75 @@ +import { ICashFlowSchemaSection, CASH_FLOW_SECTION_ID, ICashFlowStatementSectionType } from 'interfaces'; +import { ACCOUNT_TYPE } from 'data/AccountTypes'; + +export default [ + { + id: CASH_FLOW_SECTION_ID.OPERATING, + label: 'OPERATING ACTIVITIES', + sectionType: ICashFlowStatementSectionType.AGGREGATE, + children: [ + { + id: CASH_FLOW_SECTION_ID.NET_INCOME, + label: 'Net income', + sectionType: ICashFlowStatementSectionType.NET_INCOME, + }, + { + id: CASH_FLOW_SECTION_ID.OPERATING_ACCOUNTS, + label: 'Adjustments net income by operating activities.', + sectionType: ICashFlowStatementSectionType.ACCOUNTS, + accountsRelations: [ + { type: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE, direction: 'mines' }, + { type: ACCOUNT_TYPE.INVENTORY, direction: 'mines' }, + { type: ACCOUNT_TYPE.NON_CURRENT_ASSET, direction: 'mines' }, + { type: ACCOUNT_TYPE.ACCOUNTS_PAYABLE, direction: 'plus' }, + { type: ACCOUNT_TYPE.CREDIT_CARD, direction: 'plus' }, + { type: ACCOUNT_TYPE.TAX_PAYABLE, direction: 'plus' }, + { type: ACCOUNT_TYPE.OTHER_CURRENT_ASSET, direction: 'mines' }, + { type: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY, direction: 'plus' }, + { type: ACCOUNT_TYPE.NON_CURRENT_LIABILITY, direction: 'plus' }, + ], + showAlways: true, + }, + ], + footerLabel: 'Net cash provided by operating activities', + }, + { + id: CASH_FLOW_SECTION_ID.INVESTMENT, + sectionType: ICashFlowStatementSectionType.ACCOUNTS, + label: 'INVESTMENT ACTIVITIES', + accountsRelations: [ + { type: ACCOUNT_TYPE.FIXED_ASSET, direction: 'mines' } + ], + footerLabel: 'Net cash provided by investing activities', + }, + { + id: CASH_FLOW_SECTION_ID.FINANCIAL, + label: 'FINANCIAL ACTIVITIES', + sectionType: ICashFlowStatementSectionType.ACCOUNTS, + accountsRelations: [ + { type: ACCOUNT_TYPE.LOGN_TERM_LIABILITY, direction: 'plus' }, + { type: ACCOUNT_TYPE.EQUITY, direction: 'plus' }, + ], + footerLabel: 'Net cash provided by financing activities', + }, + { + id: CASH_FLOW_SECTION_ID.CASH_BEGINNING_PERIOD, + sectionType: ICashFlowStatementSectionType.CASH_AT_BEGINNING, + label: 'Cash at beginning of period', + accountsRelations: [ + { type: ACCOUNT_TYPE.CASH, direction: 'plus' }, + { type: ACCOUNT_TYPE.BANK, direction: 'plus' }, + ], + }, + { + id: CASH_FLOW_SECTION_ID.NET_CASH_INCREASE, + sectionType: ICashFlowStatementSectionType.TOTAL, + equation: 'OPERATING + INVESTMENT + FINANCIAL', + label: 'NET CASH INCREASE FOR PERIOD', + }, + { + id: CASH_FLOW_SECTION_ID.CASH_END_PERIOD, + label: 'CASH AT END OF PERIOD', + sectionType: ICashFlowStatementSectionType.TOTAL, + equation: 'NET_CASH_INCREASE + CASH_BEGINNING_PERIOD', + }, +] as ICashFlowSchemaSection[]; diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts new file mode 100644 index 000000000..5c5f6842f --- /dev/null +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryRepository.ts @@ -0,0 +1,69 @@ +import { Inject, Service } from 'typedi'; +import { map, isEmpty } from 'lodash'; +import { ICustomer, IAccount } from 'interfaces'; +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { ACCOUNT_TYPE } from 'data/AccountTypes'; + +@Service() +export default class CustomerBalanceSummaryRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the report customers. + * @param {number} tenantId + * @param {number[]} customersIds + * @returns {ICustomer[]} + */ + public getCustomers(tenantId: number, customersIds: number[]): ICustomer[] { + const { Customer } = this.tenancy.models(tenantId); + + return Customer.query() + .orderBy('displayName') + .onBuild((query) => { + if (!isEmpty(customersIds)) { + query.whereIn('id', customersIds); + } + }); + } + + /** + * Retrieve the A/R accounts. + * @param {number} tenantId + * @returns {Promise} + */ + public getReceivableAccounts(tenantId: number): Promise { + const { Account } = this.tenancy.models(tenantId); + + return Account.query().where( + 'accountType', + ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE + ); + } + + /** + * Retrieve the customers credit/debit totals + * @param {number} tenantId + * @returns + */ + public async getCustomersTransactions(tenantId: number, asDate: any) { + const { AccountTransaction } = this.tenancy.models(tenantId); + + // Retrieve the receivable accounts A/R. + const receivableAccounts = await this.getReceivableAccounts(tenantId); + const receivableAccountsIds = map(receivableAccounts, 'id'); + + // Retrieve the customers transactions of A/R accounts. + const customersTranasctions = await AccountTransaction.query().onBuild( + (query) => { + query.whereIn('accountId', receivableAccountsIds); + query.modify('filterDateRange', null, asDate); + query.groupBy('contactId'); + query.sum('credit as credit'); + query.sum('debit as debit'); + query.select('contactId'); + } + ); + return customersTranasctions; + } +} diff --git a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts index 7f648194a..e3ad79584 100644 --- a/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts +++ b/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryService.ts @@ -7,20 +7,26 @@ import { ICustomerBalanceSummaryService, ICustomerBalanceSummaryQuery, ICustomerBalanceSummaryStatement, - ICustomer + ICustomer, + ILedgerEntry, } from 'interfaces'; import { CustomerBalanceSummaryReport } from './CustomerBalanceSummary'; -import { ACCOUNT_TYPE } from 'data/AccountTypes'; + import Ledger from 'services/Accounting/Ledger'; +import CustomerBalanceSummaryRepository from './CustomerBalanceSummaryRepository'; export default class CustomerBalanceSummaryService - implements ICustomerBalanceSummaryService { + implements ICustomerBalanceSummaryService +{ @Inject() tenancy: TenancyService; @Inject('logger') logger: any; + @Inject() + reportRepository: CustomerBalanceSummaryRepository; + /** * Defaults balance sheet filter query. * @return {ICustomerBalanceSummaryQuery} @@ -43,64 +49,24 @@ export default class CustomerBalanceSummaryService }; } - /** - * Retrieve the A/R accounts. - * @param tenantId - * @returns - */ - private getReceivableAccounts(tenantId: number) { - const { Account } = this.tenancy.models(tenantId); - - return Account.query().where( - 'accountType', - ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE - ); - } /** - * Retrieve the customers credit/debit totals + * Retrieve the customers ledger entries mapped from accounts transactions. * @param {number} tenantId - * @returns + * @param {Date|string} asDate + * @returns {Promise} */ - private async getReportCustomersTransactions(tenantId: number, asDate: any) { - const { AccountTransaction } = this.tenancy.models(tenantId); - - // Retrieve the receivable accounts A/R. - const receivableAccounts = await this.getReceivableAccounts(tenantId); - const receivableAccountsIds = map(receivableAccounts, 'id'); - - // Retrieve the customers transactions of A/R accounts. - const customersTranasctions = await AccountTransaction.query().onBuild( - (query) => { - query.whereIn('accountId', receivableAccountsIds); - query.modify('filterDateRange', null, asDate); - query.groupBy('contactId'); - query.sum('credit as credit'); - query.sum('debit as debit'); - query.select('contactId'); - } + private async getReportCustomersEntries( + tenantId: number, + asDate: Date | string + ): Promise { + const transactions = await this.reportRepository.getCustomersTransactions( + tenantId, + asDate ); const commonProps = { accountNormal: 'debit', date: asDate }; - return R.map(R.merge(commonProps))(customersTranasctions); - } - - /** - * Retrieve the report customers. - * @param {number} tenantId - * @param {number[]} customersIds - * @returns {ICustomer[]} - */ - private getReportCustomers(tenantId: number, customersIds: number[]): ICustomer[] { - const { Customer } = this.tenancy.models(tenantId); - - return Customer.query() - .orderBy('displayName') - .onBuild((query) => { - if (!isEmpty(customersIds)) { - query.whereIn('id', customersIds); - } - }); + return R.map(R.merge(commonProps))(transactions); } /** @@ -130,17 +96,17 @@ export default class CustomerBalanceSummaryService } ); // Retrieve the customers list ordered by the display name. - const customers = await this.getReportCustomers( + const customers = await this.reportRepository.getCustomers( tenantId, query.customersIds ); // Retrieve the customers debit/credit totals. - const customersTransactions = await this.getReportCustomersTransactions( + const customersEntries = await this.getReportCustomersEntries( tenantId, filter.asDate ); // Ledger query. - const ledger = Ledger.fromTransactions(customersTransactions); + const ledger = new Ledger(customersEntries); // Report instance. const report = new CustomerBalanceSummaryReport( @@ -153,7 +119,7 @@ export default class CustomerBalanceSummaryService return { data: report.reportData(), columns: report.reportColumns(), - query: filter + query: filter, }; } } diff --git a/server/src/services/FinancialStatements/FinancialSheet.ts b/server/src/services/FinancialStatements/FinancialSheet.ts index d1a83ad31..8fe170244 100644 --- a/server/src/services/FinancialStatements/FinancialSheet.ts +++ b/server/src/services/FinancialStatements/FinancialSheet.ts @@ -1,4 +1,9 @@ -import { IFormatNumberSettings, INumberFormatQuery } from 'interfaces'; +import moment from 'moment'; +import { + ICashFlowStatementTotal, + IFormatNumberSettings, + INumberFormatQuery, +} from 'interfaces'; import { formatNumber } from 'utils'; export default class FinancialSheet { @@ -37,7 +42,7 @@ export default class FinancialSheet { }; return formatNumber(number, settings); } - + /** * Formatting full amount with different format settings. * @param {number} amount - @@ -52,24 +57,68 @@ export default class FinancialSheet { return this.formatNumber(amount, { money: numberFormat.formatMoney === 'none' ? false : true, excerptZero: false, - ...settings + ...settings, }); } /** * Formates the amount to the percentage string. - * @param {number} amount + * @param {number} amount * @returns {string} */ - protected formatPercentage( - amount - ): string { + protected formatPercentage(amount): string { const percentage = amount * 100; return formatNumber(percentage, { symbol: '%', excerptZero: true, money: false, - }) + }); + } + + /** + * Retrieve the amount meta object. + * @param {number} amount + * @returns {ICashFlowStatementTotal} + */ + protected getAmountMeta( + amount: number, + overrideSettings?: IFormatNumberSettings + ): ICashFlowStatementTotal { + return { + amount, + formattedAmount: this.formatNumber(amount, overrideSettings), + currencyCode: this.baseCurrency, + }; + } + + /** + * Retrieve the total amount meta object. + * @param {number} amount + * @returns {ICashFlowStatementTotal} + */ + protected getTotalAmountMeta( + amount: number, + title?: string + ): ICashFlowStatementTotal { + return { + ...(title ? { title } : {}), + amount, + formattedAmount: this.formatTotalNumber(amount), + currencyCode: this.baseCurrency, + }; + } + + /** + * Retrieve the date meta. + * @param {Date} date + * @param {string} format + * @returns + */ + protected getDateMeta(date: Date, format = 'YYYY-MM-DD') { + return { + formattedDate: moment(date).format(format), + date: moment(date).toDate(), + }; } } diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts index fd6984ab0..5f987eff9 100644 --- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts +++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedger.ts @@ -22,6 +22,7 @@ export default class GeneralLedgerSheet extends FinancialSheet { transactions: IJournalPoster; contactsMap: Map; baseCurrency: string; + i18n: any; /** * Constructor method. @@ -38,8 +39,8 @@ export default class GeneralLedgerSheet extends FinancialSheet { contactsByIdMap: Map, transactions: IJournalPoster, openingBalancesJournal: IJournalPoster, - - baseCurrency: string + baseCurrency: string, + i18n ) { super(); @@ -51,6 +52,7 @@ export default class GeneralLedgerSheet extends FinancialSheet { this.transactions = transactions; this.openingBalancesJournal = openingBalancesJournal; this.baseCurrency = baseCurrency; + this.i18n = i18n; } /** @@ -90,7 +92,7 @@ export default class GeneralLedgerSheet extends FinancialSheet { referenceType: entry.referenceType, referenceId: entry.referenceId, - referenceTypeFormatted: entry.referenceTypeFormatted, + referenceTypeFormatted: this.i18n.__(entry.referenceTypeFormatted), contactName: get(contact, 'displayName'), contactType: get(contact, 'contactService'), diff --git a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts index cd9309e95..58aaf8f0d 100644 --- a/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts +++ b/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerService.ts @@ -106,6 +106,7 @@ export default class GeneralLedgerService { contactRepository } = this.tenancy.repositories(tenantId); const settings = this.tenancy.settings(tenantId); + const i18n = this.tenancy.i18n(tenantId); const filter = { ...this.defaultQuery, @@ -157,7 +158,8 @@ export default class GeneralLedgerService { contactsByIdMap, transactionsJournal, openingTransJournal, - baseCurrency + baseCurrency, + i18n ); // Retrieve general ledger report data. const reportData = generalLedgerInstance.reportData(); diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts new file mode 100644 index 000000000..08ee86a60 --- /dev/null +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetails.ts @@ -0,0 +1,422 @@ +import * as R from 'ramda'; +import { defaultTo, sumBy, get } from 'lodash'; +import moment from 'moment'; +import { + IInventoryDetailsQuery, + IItem, + IInventoryTransaction, + TInventoryTransactionDirection, + IInventoryDetailsNumber, + IInventoryDetailsDate, + IInventoryDetailsData, + IInventoryDetailsItem, + IInventoryDetailsClosing, + INumberFormatQuery, + IInventoryDetailsOpening, + IInventoryDetailsItemTransaction, + IFormatNumberSettings, +} from 'interfaces'; +import FinancialSheet from '../FinancialSheet'; +import { transformToMapBy, transformToMapKeyValue } from 'utils'; +import { filterDeep } from 'utils/deepdash'; + +const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; + +enum INodeTypes { + ITEM = 'item', + TRANSACTION = 'transaction', + OPENING_ENTRY = 'OPENING_ENTRY', + CLOSING_ENTRY = 'CLOSING_ENTRY', +} + +export default class InventoryDetails extends FinancialSheet { + readonly inventoryTransactionsByItemId: Map; + readonly openingBalanceTransactions: Map; + readonly query: IInventoryDetailsQuery; + readonly numberFormat: INumberFormatQuery; + readonly baseCurrency: string; + readonly items: IItem[]; + + /** + * Constructor method. + * @param {IItem[]} items - Items. + * @param {IInventoryTransaction[]} inventoryTransactions - Inventory transactions. + * @param {IInventoryDetailsQuery} query - Report query. + * @param {string} baseCurrency - The base currency. + */ + constructor( + items: IItem[], + openingBalanceTransactions: IInventoryTransaction[], + inventoryTransactions: IInventoryTransaction[], + query: IInventoryDetailsQuery, + baseCurrency: string + ) { + super(); + + this.inventoryTransactionsByItemId = transformToMapBy( + inventoryTransactions, + 'itemId' + ); + this.openingBalanceTransactions = transformToMapKeyValue( + openingBalanceTransactions, + 'itemId' + ); + this.query = query; + this.numberFormat = this.query.numberFormat; + this.items = items; + this.baseCurrency = baseCurrency; + } + + /** + * Retrieve the number meta. + * @param {number} number + * @returns + */ + private getNumberMeta( + number: number, + settings?: IFormatNumberSettings + ): IInventoryDetailsNumber { + return { + formattedNumber: this.formatNumber(number, { + excerptZero: true, + money: false, + ...settings, + }), + number: number, + }; + } + + /** + * Retrieve the total number meta. + * @param {number} number - + * @param {IFormatNumberSettings} settings - + * @retrun {IInventoryDetailsNumber} + */ + private getTotalNumberMeta( + number: number, + settings?: IFormatNumberSettings + ): IInventoryDetailsNumber { + return this.getNumberMeta(number, { excerptZero: false, ...settings }); + } + + /** + * Retrieve the date meta. + * @param {Date|string} date + * @returns {IInventoryDetailsDate} + */ + private getDateMeta(date: Date | string): IInventoryDetailsDate { + return { + formattedDate: moment(date).format('YYYY-MM-DD'), + date: moment(date).toDate(), + }; + } + + /** + * Adjusts the movement amount. + * @param {number} amount + * @param {TInventoryTransactionDirection} direction + * @returns {number} + */ + private adjustAmountMovement( + direction: TInventoryTransactionDirection, + amount: number + ): number { + return direction === 'OUT' ? amount * -1 : amount; + } + + /** + * Accumlate and mapping running quantity on transactions. + * @param {IInventoryDetailsItemTransaction[]} transactions + * @returns {IInventoryDetailsItemTransaction[]} + */ + private mapAccumTransactionsRunningQuantity( + transactions: IInventoryDetailsItemTransaction[] + ): IInventoryDetailsItemTransaction[] { + const initial = this.getNumberMeta(0); + + const mapAccumAppender = (a, b) => { + const total = a.runningQuantity.number + b.quantityMovement.number; + const totalMeta = this.getNumberMeta(total, { excerptZero: false }); + const accum = { ...b, runningQuantity: totalMeta }; + + return [accum, accum]; + }; + return R.mapAccum( + mapAccumAppender, + { runningQuantity: initial }, + transactions + )[1]; + } + + /** + * Accumlate and mapping running valuation on transactions. + * @param {IInventoryDetailsItemTransaction[]} transactions + * @returns {IInventoryDetailsItemTransaction} + */ + private mapAccumTransactionsRunningValuation( + transactions: IInventoryDetailsItemTransaction[] + ): IInventoryDetailsItemTransaction[] { + const initial = this.getNumberMeta(0); + + const mapAccumAppender = (a, b) => { + const adjusmtent = b.direction === 'OUT' ? -1 : 1; + const total = a.runningValuation.number + b.cost.number * adjusmtent; + const totalMeta = this.getNumberMeta(total, { excerptZero: false }); + const accum = { ...b, runningValuation: totalMeta }; + + return [accum, accum]; + }; + return R.mapAccum( + mapAccumAppender, + { runningValuation: initial }, + transactions + )[1]; + } + + /** + * Mappes the item transaction to inventory item transaction node. + * @param {IItem} item + * @param {IInvetoryTransaction} transaction + * @returns {IInventoryDetailsItemTransaction} + */ + private itemTransactionMapper( + item: IItem, + transaction: IInventoryTransaction + ): IInventoryDetailsItemTransaction { + const total = transaction.quantity * transaction.rate; + const amountMovement = R.curry(this.adjustAmountMovement)( + transaction.direction + ); + // Quantity movement. + const quantityMovement = amountMovement(transaction.quantity); + const cost = defaultTo(transaction?.costLotAggregated.cost, 0); + + // Profit margin. + const profitMargin = total - cost; + + // Value from computed cost in `OUT` or from total sell price in `IN` transaction. + const value = transaction.direction === 'OUT' ? cost : total; + + // Value movement depends on transaction direction. + const valueMovement = amountMovement(value); + + return { + nodeType: INodeTypes.TRANSACTION, + date: this.getDateMeta(transaction.date), + transactionType: transaction.transcationTypeFormatted, + transactionNumber: transaction.meta.transactionNumber, + direction: transaction.direction, + + quantityMovement: this.getNumberMeta(quantityMovement), + valueMovement: this.getNumberMeta(valueMovement), + + quantity: this.getNumberMeta(transaction.quantity), + total: this.getNumberMeta(total), + + rate: this.getNumberMeta(transaction.rate), + cost: this.getNumberMeta(transaction.costLotAggregated.cost), + value: this.getNumberMeta(value), + + profitMargin: this.getNumberMeta(profitMargin), + + runningQuantity: this.getNumberMeta(0), + runningValuation: this.getNumberMeta(0), + }; + } + + /** + * Retrieve the inventory transcations by item id. + * @param {number} itemId + * @returns {IInventoryTransaction[]} + */ + private getInventoryTransactionsByItemId( + itemId: number + ): IInventoryTransaction[] { + return defaultTo(this.inventoryTransactionsByItemId.get(itemId + ''), []); + } + + /** + * Retrieve the item transaction node by the given item. + * @param {IItem} item + * @returns {IInventoryDetailsItemTransaction[]} + */ + private getItemTransactions(item: IItem): IInventoryDetailsItemTransaction[] { + const transactions = this.getInventoryTransactionsByItemId(item.id); + + return R.compose( + this.mapAccumTransactionsRunningQuantity.bind(this), + this.mapAccumTransactionsRunningValuation.bind(this), + R.map(R.curry(this.itemTransactionMapper.bind(this))(item)) + )(transactions); + } + + /** + * Mappes the given item transactions. + * @param {IItem} item - + * @returns {( + * IInventoryDetailsItemTransaction + * | IInventoryDetailsOpening + * | IInventoryDetailsClosing + * )[]} + */ + private itemTransactionsMapper( + item: IItem + ): ( + | IInventoryDetailsItemTransaction + | IInventoryDetailsOpening + | IInventoryDetailsClosing + )[] { + const transactions = this.getItemTransactions(item); + const openingValuation = this.getItemOpeingValuation(item); + const closingValuation = this.getItemClosingValuation( + item, + transactions, + openingValuation + ); + const hasTransactions = transactions.length > 0; + const isItemHasOpeningBalance = this.isItemHasOpeningBalance(item.id); + + return R.pipe( + R.concat(transactions), + R.when(R.always(isItemHasOpeningBalance), R.prepend(openingValuation)), + R.when(R.always(hasTransactions), R.append(closingValuation)) + )([]); + } + + /** + * Detarmines the given item has opening balance transaction. + * @param {number} itemId - Item id. + * @return {boolean} + */ + private isItemHasOpeningBalance(itemId: number): boolean { + return !!this.openingBalanceTransactions.get(itemId); + } + + /** + * Retrieve the given item opening valuation. + * @param {IItem} item - + * @returns {IInventoryDetailsOpening} + */ + private getItemOpeingValuation(item: IItem): IInventoryDetailsOpening { + const openingBalance = this.openingBalanceTransactions.get(item.id); + const quantity = defaultTo(get(openingBalance, 'quantity'), 0); + const value = defaultTo(get(openingBalance, 'value'), 0); + + return { + nodeType: INodeTypes.OPENING_ENTRY, + date: this.getDateMeta(this.query.fromDate), + quantity: this.getTotalNumberMeta(quantity), + value: this.getTotalNumberMeta(value), + }; + } + + /** + * Retrieve the given item closing valuation. + * @param {IItem} item - + * @returns {IInventoryDetailsOpening} + */ + private getItemClosingValuation( + item: IItem, + transactions: IInventoryDetailsItemTransaction[], + openingValuation: IInventoryDetailsOpening + ): IInventoryDetailsOpening { + const value = sumBy(transactions, 'valueMovement.number'); + const quantity = sumBy(transactions, 'quantityMovement.number'); + const profitMargin = sumBy(transactions, 'profitMargin.number'); + + const closingQuantity = quantity + openingValuation.quantity.number; + const closingValue = value + openingValuation.value.number; + + return { + nodeType: INodeTypes.CLOSING_ENTRY, + date: this.getDateMeta(this.query.toDate), + quantity: this.getTotalNumberMeta(closingQuantity), + value: this.getTotalNumberMeta(closingValue), + profitMargin: this.getTotalNumberMeta(profitMargin), + }; + } + + /** + * Retrieve the item node of the report. + * @param {IItem} item + * @returns {IInventoryDetailsItem} + */ + private itemsNodeMapper(item: IItem): IInventoryDetailsItem { + return { + id: item.id, + name: item.name, + code: item.code, + nodeType: INodeTypes.ITEM, + children: this.itemTransactionsMapper(item), + }; + } + + /** + * Detarmines the given node equals the given type. + * @param {string} nodeType + * @param {IItem} node + * @returns {boolean} + */ + private isNodeTypeEquals( + nodeType: string, + node: IInventoryDetailsItem + ): boolean { + return nodeType === node.nodeType; + } + + /** + * Detarmines whether the given item node has transactions. + * @param {IInventoryDetailsItem} item + * @returns {boolean} + */ + private isItemNodeHasTransactions(item: IInventoryDetailsItem) { + return !!this.inventoryTransactionsByItemId.get(item.id); + } + + /** + * Detarmines the filter + * @param {IInventoryDetailsItem} item + * @return {boolean} + */ + private isFilterNode(item: IInventoryDetailsItem): boolean { + return R.ifElse( + R.curry(this.isNodeTypeEquals)(INodeTypes.ITEM), + this.isItemNodeHasTransactions.bind(this), + R.always(true) + )(item); + } + + /** + * Filters items nodes. + * @param {IInventoryDetailsItem[]} items - + * @returns {IInventoryDetailsItem[]} + */ + private filterItemsNodes(items: IInventoryDetailsItem[]) { + const filtered = filterDeep( + items, + this.isFilterNode.bind(this), + MAP_CONFIG + ); + return defaultTo(filtered, []); + } + + /** + * Retrieve the items nodes of the report. + * @param {IItem} items + * @returns {IInventoryDetailsItem[]} + */ + private itemsNodes(items: IItem[]): IInventoryDetailsItem[] { + return R.compose( + this.filterItemsNodes.bind(this), + R.map(this.itemsNodeMapper.bind(this)) + )(items); + } + + /** + * Retrieve the inventory item details report data. + * @returns {IInventoryDetailsData} + */ + public reportData(): IInventoryDetailsData { + return this.itemsNodes(this.items); + } +} diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts new file mode 100644 index 000000000..8cc98ca0a --- /dev/null +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsRepository.ts @@ -0,0 +1,93 @@ +import { Inject } from 'typedi'; +import { raw } from 'objection'; +import moment from 'moment'; +import { + IItem, + IInventoryDetailsQuery, + IInventoryTransaction, +} from 'interfaces'; +import HasTenancyService from 'services/Tenancy/TenancyService'; + +export default class InventoryDetailsRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve inventory items. + * @param {number} tenantId - + * @returns {Promise} + */ + public getInventoryItems(tenantId: number): Promise { + const { Item } = this.tenancy.models(tenantId); + + return Item.query().where('type', 'inventory'); + } + + /** + * Retrieve the items opening balance transactions. + * @param {number} tenantId - + * @param {IInventoryDetailsQuery} + * @return {Promise} + */ + public async openingBalanceTransactions( + tenantId: number, + filter: IInventoryDetailsQuery + ): Promise { + const { InventoryTransaction } = this.tenancy.models(tenantId); + + const openingBalanceDate = moment(filter.fromDate) + .subtract(1, 'days') + .toDate(); + + // Opening inventory transactions. + const openingTransactions = InventoryTransaction.query() + .select('*') + .select(raw("IF(`DIRECTION` = 'IN', `QUANTITY`, 0) as 'QUANTITY_IN'")) + .select(raw("IF(`DIRECTION` = 'OUT', `QUANTITY`, 0) as 'QUANTITY_OUT'")) + .select( + raw("IF(`DIRECTION` = 'IN', `QUANTITY` * `RATE`, 0) as 'VALUE_IN'") + ) + .select( + raw("IF(`DIRECTION` = 'OUT', `QUANTITY` * `RATE`, 0) as 'VALUE_OUT'") + ) + .modify('filterDateRange', null, openingBalanceDate) + .orderBy('date', 'ASC') + .as('inventory_transactions'); + + const openingBalanceTransactions = await InventoryTransaction.query() + .from(openingTransactions) + .select('itemId') + .select(raw('SUM(`QUANTITY_IN` - `QUANTITY_OUT`) AS `QUANTITY`')) + .select(raw('SUM(`VALUE_IN` - `VALUE_OUT`) AS `VALUE`')) + .groupBy('itemId') + .sum('rate as rate') + .sum('quantityIn as quantityIn') + .sum('quantityOut as quantityOut') + .sum('valueIn as valueIn') + .sum('valueOut as valueOut') + .withGraphFetched('itemCostAggregated'); + + return openingBalanceTransactions; + } + + /** + * Retrieve the items inventory tranasactions. + * @param {number} tenantId - + * @param {IInventoryDetailsQuery} + * @return {Promise} + */ + public async itemInventoryTransactions( + tenantId: number, + filter: IInventoryDetailsQuery + ): Promise { + const { InventoryTransaction } = this.tenancy.models(tenantId); + + const inventoryTransactions = InventoryTransaction.query() + .modify('filterDateRange', filter.fromDate, filter.toDate) + .orderBy('date', 'ASC') + .withGraphFetched('meta') + .withGraphFetched('costLotAggregated'); + + return inventoryTransactions; + } +} diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts new file mode 100644 index 000000000..c1628042e --- /dev/null +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsService.ts @@ -0,0 +1,119 @@ +import moment from 'moment'; +import { Service, Inject } from 'typedi'; +import { + IInventoryDetailsQuery, + IInvetoryItemDetailDOO, + IInventoryItemDetailMeta, +} from 'interfaces'; +import TenancyService from 'services/Tenancy/TenancyService'; +import InventoryDetails from './InventoryDetails'; +import FinancialSheet from '../FinancialSheet'; +import InventoryDetailsRepository from './InventoryDetailsRepository'; +import InventoryService from 'services/Inventory/Inventory'; +import { parseBoolean } from 'utils'; + +@Service() +export default class InventoryDetailsService extends FinancialSheet { + @Inject() + tenancy: TenancyService; + + @Inject() + reportRepo: InventoryDetailsRepository; + + @Inject() + inventoryService: InventoryService; + + /** + * Defaults balance sheet filter query. + * @return {IBalanceSheetQuery} + */ + private get defaultQuery(): IInventoryDetailsQuery { + return { + fromDate: moment().startOf('year').format('YYYY-MM-DD'), + toDate: moment().endOf('year').format('YYYY-MM-DD'), + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + noneTransactions: false, + }; + } + + /** + * Retrieve the balance sheet meta. + * @param {number} tenantId - + * @returns {IInventoryItemDetailMeta} + */ + private reportMetadata(tenantId: number): IInventoryItemDetailMeta { + const settings = this.tenancy.settings(tenantId); + + const isCostComputeRunning = + this.inventoryService.isItemsCostComputeRunning(tenantId); + + const organizationName = settings.get({ + group: 'organization', + key: 'name', + }); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + return { + isCostComputeRunning: parseBoolean(isCostComputeRunning, false), + organizationName, + baseCurrency, + }; + } + + /** + * Retrieve the inventory details report data. + * @param {number} tenantId - + * @param {IInventoryDetailsQuery} query - + * @return {Promise} + */ + public async inventoryDetails( + tenantId: number, + query: IInventoryDetailsQuery + ): Promise { + // Settings tenant service. + const settings = this.tenancy.settings(tenantId); + const baseCurrency = settings.get({ + group: 'organization', + key: 'base_currency', + }); + + const filter = { + ...this.defaultQuery, + ...query, + }; + // Retrieves the items. + const items = await this.reportRepo.getInventoryItems(tenantId); + + // Opening balance transactions. + const openingBalanceTransactions = + await this.reportRepo.openingBalanceTransactions(tenantId, filter); + + // Retrieves the inventory transaction. + const inventoryTransactions = + await this.reportRepo.itemInventoryTransactions(tenantId, filter); + + // Inventory details report mapper. + const inventoryDetailsInstance = new InventoryDetails( + items, + openingBalanceTransactions, + inventoryTransactions, + filter, + baseCurrency + ); + + return { + data: inventoryDetailsInstance.reportData(), + query: filter, + meta: this.reportMetadata(tenantId), + }; + } +} diff --git a/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts new file mode 100644 index 000000000..5b8532706 --- /dev/null +++ b/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTable.ts @@ -0,0 +1,190 @@ +import * as R from 'ramda'; +import { + IInventoryDetailsItem, + IInventoryDetailsItemTransaction, + IInventoryDetailsClosing, + ITableColumn, + ITableRow, + IInventoryDetailsNode, + IInventoryDetailsOpening, +} from 'interfaces'; +import { mapValuesDeep } from 'utils/deepdash'; +import { tableRowMapper } from 'utils'; + +enum IROW_TYPE { + ITEM = 'ITEM', + TRANSACTION = 'TRANSACTION', + CLOSING_ENTRY = 'CLOSING_ENTRY', + OPENING_ENTRY = 'OPENING_ENTRY', +} + +const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; + +export default class InventoryDetailsTable { + i18n: any; + report: any; + + /** + * Constructor method. + * @param {ICashFlowStatement} reportStatement - Report statement. + */ + constructor(reportStatement, i18n) { + this.report = reportStatement; + this.i18n = i18n; + } + + /** + * Mappes the item node to table rows. + * @param {IInventoryDetailsItem} item + * @returns {ITableRow} + */ + private itemNodeMapper(item: IInventoryDetailsItem) { + const columns = [{ key: 'item_name', accessor: 'name' }]; + + return tableRowMapper(item, columns, { + rowTypes: [IROW_TYPE.ITEM], + }); + } + + /** + * Mappes the item inventory transaction to table row. + * @param {IInventoryDetailsItemTransaction} transaction + * @returns {ITableRow} + */ + private itemTransactionNodeMapper( + transaction: IInventoryDetailsItemTransaction + ) { + const columns = [ + { key: 'date', accessor: 'date.formattedDate' }, + { key: 'transaction_type', accessor: 'transactionType' }, + { key: 'transaction_id', accessor: 'transactionNumber' }, + { + key: 'quantity_movement', + accessor: 'quantityMovement.formattedNumber', + }, + { key: 'rate', accessor: 'rate.formattedNumber' }, + { key: 'total', accessor: 'total.formattedNumber' }, + { key: 'value', accessor: 'valueMovement.formattedNumber' }, + { key: 'profit_margin', accessor: 'profitMargin.formattedNumber' }, + { key: 'running_quantity', accessor: 'runningQuantity.formattedNumber' }, + { + key: 'running_valuation', + accessor: 'runningValuation.formattedNumber', + }, + ]; + return tableRowMapper(transaction, columns, { + rowTypes: [IROW_TYPE.TRANSACTION], + }); + } + + /** + * Opening balance transaction mapper to table row. + * @param {IInventoryDetailsOpening} transaction + * @returns {ITableRow} + */ + private openingNodeMapper(transaction: IInventoryDetailsOpening): ITableRow { + const columns = [ + { key: 'date', accessor: 'date.formattedDate' }, + { key: 'closing', value: 'Opening Balance' }, + { key: 'empty' }, + { key: 'quantity', accessor: 'quantity.formattedNumber' }, + { key: 'empty' }, + { key: 'empty' }, + { key: 'value', accessor: 'value.formattedNumber' }, + ]; + return tableRowMapper(transaction, columns, { + rowTypes: [IROW_TYPE.OPENING_ENTRY], + }); + } + + /** + * Closing balance transaction mapper to table raw. + * @param {IInventoryDetailsClosing} transaction + * @returns {ITableRow} + */ + private closingNodeMapper(transaction: IInventoryDetailsClosing): ITableRow { + const columns = [ + { key: 'date', accessor: 'date.formattedDate' }, + { key: 'closing', value: 'Closing Balance' }, + { key: 'empty' }, + { key: 'quantity', accessor: 'quantity.formattedNumber' }, + { key: 'empty' }, + { key: 'empty' }, + { key: 'value', accessor: 'value.formattedNumber' }, + { key: 'profitMargin', accessor: 'profitMargin.formattedNumber' }, + ]; + + return tableRowMapper(transaction, columns, { + rowTypes: [IROW_TYPE.CLOSING_ENTRY], + }); + } + + /** + * Detarmines the ginve inventory details node type. + * @param {string} type + * @param {IInventoryDetailsNode} node + * @returns {boolean} + */ + private isNodeTypeEquals(type: string, node: IInventoryDetailsNode): boolean { + return node.nodeType === type; + } + + /** + * Mappes the given item or transactions node to table rows. + * @param {IInventoryDetailsNode} node - + * @return {ITableRow} + */ + private itemMapper(node: IInventoryDetailsNode): ITableRow { + return R.compose( + R.when( + R.curry(this.isNodeTypeEquals)('OPENING_ENTRY'), + this.openingNodeMapper + ), + R.when( + R.curry(this.isNodeTypeEquals)('CLOSING_ENTRY'), + this.closingNodeMapper + ), + R.when(R.curry(this.isNodeTypeEquals)('item'), this.itemNodeMapper), + R.when( + R.curry(this.isNodeTypeEquals)('transaction'), + this.itemTransactionNodeMapper.bind(this) + ) + )(node); + } + + /** + * Mappes the items nodes to table rows. + * @param {IInventoryDetailsItem[]} items + * @returns {ITableRow[]} + */ + private itemsMapper(items: IInventoryDetailsItem[]): ITableRow[] { + return mapValuesDeep(items, this.itemMapper.bind(this), MAP_CONFIG); + } + + /** + * Retrieve the table rows of the inventory item details. + * @returns {ITableRow[]} + */ + public tableData(): ITableRow[] { + return this.itemsMapper(this.report.data); + } + + /** + * Retrieve the table columns of inventory details report. + * @returns {ITableColumn[]} + */ + public tableColumns(): ITableColumn[] { + return [ + { key: 'date', label: this.i18n.__('Date') }, + { key: 'transaction_type', label: this.i18n.__('Transaction type') }, + { key: 'transaction_id', label: this.i18n.__('Transaction #') }, + { key: 'quantity', label: this.i18n.__('Quantity') }, + { key: 'rate', label: this.i18n.__('Rate') }, + { key: 'total', label: this.i18n.__('Total') }, + { key: 'value', label: this.i18n.__('Value') }, + { key: 'profit_margin', label: this.i18n.__('Profit Margin') }, + { key: 'running_quantity', label: this.i18n.__('Running quantity') }, + { key: 'running_value', label: this.i18n.__('Running Value') }, + ]; + } +} diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts index 3c8df9d5e..14c0f6c1a 100644 --- a/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts +++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheet.ts @@ -27,7 +27,8 @@ export default class JournalSheet extends FinancialSheet { journal: IJournalPoster, accountsGraph: any, contactsById: Map, - baseCurrency: string + baseCurrency: string, + i18n ) { super(); @@ -38,6 +39,7 @@ export default class JournalSheet extends FinancialSheet { this.accountsGraph = accountsGraph; this.contactsById = contactsById; this.baseCurrency = baseCurrency; + this.i18n = i18n; } /** @@ -96,7 +98,7 @@ export default class JournalSheet extends FinancialSheet { date: groupEntry.date, referenceType: groupEntry.referenceType, referenceId: groupEntry.referenceId, - referenceTypeFormatted: groupEntry.referenceTypeFormatted, + referenceTypeFormatted: this.i18n.__(groupEntry.referenceTypeFormatted), entries: this.entriesMapper(entriesGroup), diff --git a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts index 7723b1598..f03abf77c 100644 --- a/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts +++ b/server/src/services/FinancialStatements/JournalSheet/JournalSheetService.ts @@ -70,6 +70,7 @@ export default class JournalSheetService { * @param {IJournalSheetFilterQuery} query */ async journalSheet(tenantId: number, query: IJournalReportQuery) { + const i18n = this.tenancy.i18n(tenantId); const { accountRepository, transactionsRepository, @@ -120,7 +121,8 @@ export default class JournalSheetService { transactionsJournal, accountsGraph, contactsByIdMap, - baseCurrency + baseCurrency, + i18n ); // Retrieve journal report columns. const journalSheetData = journalSheetInstance.reportData(); diff --git a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts index b10820f32..a3ff121a9 100644 --- a/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts +++ b/server/src/services/FinancialStatements/TransactionsByContact/TransactionsByContact.ts @@ -5,6 +5,7 @@ import { ITransactionsByContactsFilter, IContact, ILedger, + ILedgerEntry, } from 'interfaces'; import FinancialSheet from '../FinancialSheet'; @@ -20,20 +21,20 @@ export default class TransactionsByContact extends FinancialSheet { * @return {Omit} */ protected contactTransactionMapper( - transaction + entry: ILedgerEntry, ): Omit { - const account = this.accountsGraph.getNodeData(transaction.accountId); + const account = this.accountsGraph.getNodeData(entry.accountId); const currencyCode = 'USD'; return { - credit: this.getContactAmount(transaction.credit, currencyCode), - debit: this.getContactAmount(transaction.debit, currencyCode), + credit: this.getContactAmount(entry.credit, currencyCode), + debit: this.getContactAmount(entry.debit, currencyCode), accountName: account.name, currencyCode: 'USD', - transactionNumber: transaction.transactionNumber, - transactionType: transaction.transactionType, - date: transaction.date, - createdAt: transaction.createdAt, + transactionNumber: entry.transactionNumber, + transactionType: entry.referenceTypeFormatted, + date: entry.date, + createdAt: entry.createdAt, }; } diff --git a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersRepository.ts b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersRepository.ts new file mode 100644 index 000000000..603c339db --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersRepository.ts @@ -0,0 +1,92 @@ +import { map } from 'lodash'; +import { IAccount, IAccountTransaction } from 'interfaces'; +import { ACCOUNT_TYPE } from 'data/AccountTypes'; +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { Inject } from 'typedi'; + +export default class TransactionsByCustomersRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the report customers. + * @param {number} tenantId + * @returns {Promise} + */ + public async getCustomers(tenantId: number) { + const { Customer } = this.tenancy.models(tenantId); + + return Customer.query().orderBy('displayName'); + } + + /** + * Retrieve the accounts receivable. + * @param {number} tenantId + * @returns {Promise} + */ + public async getReceivableAccounts(tenantId: number): Promise { + const { Account } = this.tenancy.models(tenantId); + + const accounts = await Account.query().where( + 'accountType', + ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE + ); + return accounts; + } + + /** + * Retrieve the customers opening balance transactions. + * @param {number} tenantId - Tenant id. + * @param {number} openingDate - Opening date. + * @param {number} customersIds - Customers ids. + * @returns {Promise} + */ + public async getCustomersOpeningBalanceTransactions( + tenantId: number, + openingDate: Date, + customersIds?: number[] + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + + const receivableAccounts = await this.getReceivableAccounts(tenantId); + const receivableAccountsIds = map(receivableAccounts, 'id'); + + const openingTransactions = await AccountTransaction.query().modify( + 'contactsOpeningBalance', + openingDate, + receivableAccountsIds, + customersIds + ); + return openingTransactions; + } + + /** + * Retrieve the customers periods transactions. + * @param {number} tenantId - Tenant id. + * @param {Date|string} openingDate - Opening date. + * @param {number[]} customersIds - Customers ids. + * @return {Promise} + */ + public async getCustomersPeriodTransactions( + tenantId: number, + fromDate: Date, + toDate: Date + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + + const receivableAccounts = await this.getReceivableAccounts(tenantId); + const receivableAccountsIds = map(receivableAccounts, 'id'); + + const transactions = await AccountTransaction.query().onBuild((query) => { + // Filter by date. + query.modify('filterDateRange', fromDate, toDate); + + // Filter by customers. + query.whereNot('contactId', null); + + // Filter by accounts. + query.whereIn('accountId', receivableAccountsIds); + }); + return transactions; + } +} diff --git a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts index 0bf91adba..0de5a070c 100644 --- a/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts +++ b/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersService.ts @@ -1,25 +1,29 @@ import { Inject } from 'typedi'; import * as R from 'ramda'; import moment from 'moment'; -import { map } from 'lodash'; import TenancyService from 'services/Tenancy/TenancyService'; import { ITransactionsByCustomersService, ITransactionsByCustomersFilter, ITransactionsByCustomersStatement, + ILedgerEntry, } from 'interfaces'; import TransactionsByCustomers from './TransactionsByCustomers'; import Ledger from 'services/Accounting/Ledger'; -import { ACCOUNT_TYPE } from 'data/AccountTypes'; +import TransactionsByCustomersRepository from './TransactionsByCustomersRepository'; export default class TransactionsByCustomersService - implements ITransactionsByCustomersService { + implements ITransactionsByCustomersService +{ @Inject() tenancy: TenancyService; @Inject('logger') logger: any; + @Inject() + reportRepository: TransactionsByCustomersRepository; + /** * Defaults balance sheet filter query. * @return {ICustomerBalanceSummaryQuery} @@ -44,43 +48,24 @@ export default class TransactionsByCustomersService } /** - * Retrieve the accounts receivable. + * Retrieve the customers opening balance ledger entries. * @param {number} tenantId - * @returns + * @param {Date} openingDate + * @param {number[]} customersIds + * @returns {Promise} */ - async getReceivableAccounts(tenantId: number) { - const { Account } = this.tenancy.models(tenantId); - - const accounts = await Account.query().where( - 'accountType', - ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE - ); - return accounts; - } - - /** - * Retrieve the customers opening balance transactions. - * @param {number} tenantId - * @param {number} openingDate - * @param {number} customersIds - * @returns {} - */ - async getCustomersOpeningBalance( + private async getCustomersOpeningBalanceEntries( tenantId: number, openingDate: Date, customersIds?: number[] ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); + const openingTransactions = + await this.reportRepository.getCustomersOpeningBalanceTransactions( + tenantId, + openingDate, + customersIds + ); - const receivableAccounts = await this.getReceivableAccounts(tenantId); - const receivableAccountsIds = map(receivableAccounts, 'id'); - - const openingTransactions = await AccountTransaction.query().modify( - 'contactsOpeningBalance', - openingDate, - receivableAccountsIds, - customersIds - ); return R.compose( R.map(R.assoc('date', openingDate)), R.map(R.assoc('accountNormal', 'debit')) @@ -88,38 +73,29 @@ export default class TransactionsByCustomersService } /** - * + * Retrieve the customers periods ledger entries. * @param {number} tenantId - * @param {Date|string} openingDate - * @param {number[]} customersIds + * @param {Date} fromDate + * @param {Date} toDate + * @returns {Promise} */ - async getCustomersPeriodTransactions( + private async getCustomersPeriodsEntries( tenantId: number, - fromDate: Date, - toDate: Date + fromDate: Date|string, + toDate: Date|string, ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); - - const receivableAccounts = await this.getReceivableAccounts(tenantId); - const receivableAccountsIds = map(receivableAccounts, 'id'); - - const transactions = await AccountTransaction.query().onBuild((query) => { - // Filter by date. - query.modify('filterDateRange', fromDate, toDate); - - // Filter by customers. - query.whereNot('contactId', null); - - // Filter by accounts. - query.whereIn('accountId', receivableAccountsIds); - }); - + const transactions = + await this.reportRepository.getCustomersPeriodTransactions( + tenantId, + fromDate, + toDate + ); return R.compose( R.map(R.assoc('accountNormal', 'debit')), R.map((trans) => ({ ...trans, referenceTypeFormatted: trans.referenceTypeFormatted, - })), + })) )(transactions); } @@ -133,7 +109,6 @@ export default class TransactionsByCustomersService tenantId: number, query: ITransactionsByCustomersFilter ): Promise { - const { Customer } = this.tenancy.models(tenantId); const { accountRepository } = this.tenancy.repositories(tenantId); // Settings tenant service. @@ -148,29 +123,31 @@ export default class TransactionsByCustomersService ...query, }; const accountsGraph = await accountRepository.getDependencyGraph(); - const customers = await Customer.query().orderBy('displayName'); + + // Retrieve the report customers. + const customers = await this.reportRepository.getCustomers(tenantId); const openingBalanceDate = moment(filter.fromDate) .subtract(1, 'days') .toDate(); // Retrieve all ledger transactions of the opening balance of. - const openingBalanceTransactions = await this.getCustomersOpeningBalance( + const openingBalanceEntries = await this.getCustomersOpeningBalanceEntries( tenantId, openingBalanceDate ); // Retrieve all ledger transactions between opeing and closing period. - const customersTransactions = await this.getCustomersPeriodTransactions( + const customersTransactions = await this.getCustomersPeriodsEntries( tenantId, query.fromDate, query.toDate ); // Concats the opening balance and period customer ledger transactions. const journalTransactions = [ - ...openingBalanceTransactions, + ...openingBalanceEntries, ...customersTransactions, ]; - const journal = Ledger.fromTransactions(journalTransactions); + const journal = new Ledger(journalTransactions); // Transactions by customers data mapper. const reportInstance = new TransactionsByCustomers( diff --git a/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorRepository.ts b/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorRepository.ts new file mode 100644 index 000000000..c67b09741 --- /dev/null +++ b/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorRepository.ts @@ -0,0 +1,92 @@ +import { Inject, Service } from 'typedi'; +import { map } from 'lodash'; +import { IVendor, IAccount, IAccountTransaction } from 'interfaces'; +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { ACCOUNT_TYPE } from 'data/AccountTypes'; + +@Service() +export default class TransactionsByVendorRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the report vendors. + * @param {number} tenantId + * @returns {Promise} + */ + public getVendors(tenantId: number): Promise { + const { Vendor } = this.tenancy.models(tenantId); + + return Vendor.query().orderBy('displayName'); + } + + /** + * Retrieve the accounts receivable. + * @param {number} tenantId + * @returns {Promise} + */ + private async getPayableAccounts(tenantId: number): Promise { + const { Account } = this.tenancy.models(tenantId); + + const accounts = await Account.query().where( + 'accountType', + ACCOUNT_TYPE.ACCOUNTS_PAYABLE + ); + return accounts; + } + + /** + * Retrieve the customers opening balance transactions. + * @param {number} tenantId + * @param {number} openingDate + * @param {number} customersIds + * @returns {} + */ + public async getVendorsOpeningBalance( + tenantId: number, + openingDate: Date, + customersIds?: number[] + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + + const payableAccounts = await this.getPayableAccounts(tenantId); + const payableAccountsIds = map(payableAccounts, 'id'); + + const openingTransactions = await AccountTransaction.query().modify( + 'contactsOpeningBalance', + openingDate, + payableAccountsIds, + customersIds + ); + return openingTransactions; + } + + /** + * Retrieve vendors periods transactions. + * @param {number} tenantId + * @param {Date|string} openingDate + * @param {number[]} customersIds + */ + public async getVendorsPeriodTransactions( + tenantId: number, + fromDate: Date, + toDate: Date + ): Promise { + const { AccountTransaction } = this.tenancy.models(tenantId); + + const receivableAccounts = await this.getPayableAccounts(tenantId); + const receivableAccountsIds = map(receivableAccounts, 'id'); + + const transactions = await AccountTransaction.query().onBuild((query) => { + // Filter by date. + query.modify('filterDateRange', fromDate, toDate); + + // Filter by customers. + query.whereNot('contactId', null); + + // Filter by accounts. + query.whereIn('accountId', receivableAccountsIds); + }); + return transactions; + } +} diff --git a/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService.ts b/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService.ts index df7686a1d..354e5fa45 100644 --- a/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService.ts +++ b/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorService.ts @@ -4,23 +4,27 @@ import * as R from 'ramda'; import { map } from 'lodash'; import TenancyService from 'services/Tenancy/TenancyService'; import { - IVendor, ITransactionsByVendorsService, ITransactionsByVendorsFilter, ITransactionsByVendorsStatement, + ILedgerEntry, } from 'interfaces'; import TransactionsByVendor from './TransactionsByVendor'; -import { ACCOUNT_TYPE } from 'data/AccountTypes'; import Ledger from 'services/Accounting/Ledger'; +import TransactionsByVendorRepository from './TransactionsByVendorRepository'; export default class TransactionsByVendorsService - implements ITransactionsByVendorsService { + implements ITransactionsByVendorsService +{ @Inject() tenancy: TenancyService; @Inject('logger') logger: any; + @Inject() + reportRepository: TransactionsByVendorRepository; + /** * Defaults balance sheet filter query. * @return {IVendorBalanceSummaryQuery} @@ -44,55 +48,24 @@ export default class TransactionsByVendorsService }; } - /** - * Retrieve the report vendors. - * @param tenantId - * @returns - */ - private getReportVendors(tenantId: number): Promise { - const { Vendor } = this.tenancy.models(tenantId); - - return Vendor.query().orderBy('displayName'); - } - - /** - * Retrieve the accounts receivable. - * @param {number} tenantId - * @returns - */ - private async getPayableAccounts(tenantId: number) { - const { Account } = this.tenancy.models(tenantId); - - const accounts = await Account.query().where( - 'accountType', - ACCOUNT_TYPE.ACCOUNTS_PAYABLE - ); - return accounts; - } - /** * Retrieve the customers opening balance transactions. * @param {number} tenantId * @param {number} openingDate * @param {number} customersIds - * @returns {} + * @returns {Promise} */ - private async getVendorsOpeningBalance( + private async getVendorsOpeningBalanceEntries( tenantId: number, openingDate: Date, customersIds?: number[] ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); - - const payableAccounts = await this.getPayableAccounts(tenantId); - const payableAccountsIds = map(payableAccounts, 'id'); - - const openingTransactions = await AccountTransaction.query().modify( - 'contactsOpeningBalance', - openingDate, - payableAccountsIds, - customersIds - ); + const openingTransactions = + await this.reportRepository.getVendorsOpeningBalance( + tenantId, + openingDate, + customersIds + ); return R.compose( R.map(R.assoc('date', openingDate)), R.map(R.assoc('accountNormal', 'credit')) @@ -105,42 +78,46 @@ export default class TransactionsByVendorsService * @param {Date|string} openingDate * @param {number[]} customersIds */ - async getVendorsPeriodTransactions( + private async getVendorsPeriodEntries( tenantId: number, fromDate: Date, toDate: Date ): Promise { - const { AccountTransaction } = this.tenancy.models(tenantId); - - const receivableAccounts = await this.getPayableAccounts(tenantId); - const receivableAccountsIds = map(receivableAccounts, 'id'); - - const transactions = await AccountTransaction.query().onBuild((query) => { - // Filter by date. - query.modify('filterDateRange', fromDate, toDate); - - // Filter by customers. - query.whereNot('contactId', null); - - // Filter by accounts. - query.whereIn('accountId', receivableAccountsIds); - }); - + const transactions = + await this.reportRepository.getVendorsPeriodTransactions( + tenantId, + fromDate, + toDate + ); return R.compose( R.map(R.assoc('accountNormal', 'credit')), R.map((trans) => ({ ...trans, referenceTypeFormatted: trans.referenceTypeFormatted, - })), + })) )(transactions); } - async getReportTransactions(tenantId: number, fromDate: Date, toDate: Date) { + /** + * Retrieve the report ledger entries from repository. + * @param {number} tenantId + * @param {Date} fromDate + * @param {Date} toDate + * @returns {Promise} + */ + private async getReportEntries( + tenantId: number, + fromDate: Date, + toDate: Date + ): Promise { const openingBalanceDate = moment(fromDate).subtract(1, 'days').toDate(); return [ - ...(await this.getVendorsOpeningBalance(tenantId, openingBalanceDate)), - ...(await this.getVendorsPeriodTransactions(tenantId, fromDate, toDate)), + ...(await this.getVendorsOpeningBalanceEntries( + tenantId, + openingBalanceDate + )), + ...(await this.getVendorsPeriodEntries(tenantId, fromDate, toDate)), ]; } @@ -155,7 +132,7 @@ export default class TransactionsByVendorsService query: ITransactionsByVendorsFilter ): Promise { const { accountRepository } = this.tenancy.repositories(tenantId); - + // Settings tenant service. const settings = this.tenancy.settings(tenantId); const baseCurrency = settings.get({ @@ -166,19 +143,19 @@ export default class TransactionsByVendorsService const filter = { ...this.defaultQuery, ...query }; // Retrieve the report vendors. - const vendors = await this.getReportVendors(tenantId); + const vendors = await this.reportRepository.getVendors(tenantId); // Retrieve the accounts graph. const accountsGraph = await accountRepository.getDependencyGraph(); // Journal transactions. - const journalTransactions = await this.getReportTransactions( + const reportEntries = await this.getReportEntries( tenantId, filter.fromDate, filter.toDate ); // Ledger collection. - const journal = Ledger.fromTransactions(journalTransactions); + const journal = new Ledger(reportEntries); // Transactions by customers data mapper. const reportInstance = new TransactionsByVendor( diff --git a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryRepository.ts b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryRepository.ts new file mode 100644 index 000000000..ed929bb9d --- /dev/null +++ b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryRepository.ts @@ -0,0 +1,69 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty, map } from 'lodash'; +import { IVendor, IAccount } from 'interfaces'; +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { ACCOUNT_TYPE } from 'data/AccountTypes'; + +@Service() +export default class VendorBalanceSummaryRepository { + @Inject() + tenancy: HasTenancyService; + + /** + * Retrieve the report vendors. + * @param {number} tenantId + * @param {number[]} vendorsIds - Vendors ids. + * @returns {IVendor[]} + */ + public getVendors( + tenantId: number, + vendorsIds?: number[] + ): Promise { + const { Vendor } = this.tenancy.models(tenantId); + + const vendorQuery = Vendor.query().orderBy('displayName'); + + if (!isEmpty(vendorsIds)) { + vendorQuery.whereIn('id', vendorsIds); + } + return vendorQuery; + } + + /** + * Retrieve the payable accounts. + * @param {number} tenantId + * @returns {Promise} + */ + public getPayableAccounts(tenantId: number): Promise { + const { Account } = this.tenancy.models(tenantId); + + return Account.query().where('accountType', ACCOUNT_TYPE.ACCOUNTS_PAYABLE); + } + + /** + * Retrieve the vendors transactions. + * @param {number} tenantId + * @param {Date} asDate + * @returns + */ + public async getVendorsTransactions(tenantId: number, asDate: Date | string) { + const { AccountTransaction } = this.tenancy.models(tenantId); + + // Retrieve payable accounts . + const payableAccounts = await this.getPayableAccounts(tenantId); + const payableAccountsIds = map(payableAccounts, 'id'); + + // Retrieve the customers transactions of A/R accounts. + const customersTranasctions = await AccountTransaction.query().onBuild( + (query) => { + query.whereIn('accountId', payableAccountsIds); + query.modify('filterDateRange', null, asDate); + query.groupBy('contactId'); + query.sum('credit as credit'); + query.sum('debit as debit'); + query.select('contactId'); + } + ); + return customersTranasctions; + } +} diff --git a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts index bf8a93673..19065abb3 100644 --- a/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts +++ b/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryService.ts @@ -8,20 +8,24 @@ import { IVendorBalanceSummaryService, IVendorBalanceSummaryQuery, IVendorBalanceSummaryStatement, + ILedgerEntry, } from 'interfaces'; import { VendorBalanceSummaryReport } from './VendorBalanceSummary'; -import { isEmpty } from 'lodash'; -import { ACCOUNT_TYPE } from 'data/AccountTypes'; import Ledger from 'services/Accounting/Ledger'; +import VendorBalanceSummaryRepository from './VendorBalanceSummaryRepository'; export default class VendorBalanceSummaryService - implements IVendorBalanceSummaryService { + implements IVendorBalanceSummaryService +{ @Inject() tenancy: TenancyService; @Inject('logger') logger: any; + @Inject() + reportRepo: VendorBalanceSummaryRepository; + /** * Defaults balance sheet filter query. * @return {IVendorBalanceSummaryQuery} @@ -45,59 +49,22 @@ export default class VendorBalanceSummaryService } /** - * Retrieve the report vendors. - * @param {number} tenantId - * @param {number[]} vendorsIds - Vendors ids. - * @returns {IVendor[]} + * Retrieve the vendors ledger entrjes. + * @param {number} tenantId - + * @param {Date|string} date - + * @returns {Promise} */ - getReportVendors( + private async getReportVendorsEntries( tenantId: number, - vendorsIds?: number[] - ): Promise { - const { Vendor } = this.tenancy.models(tenantId); - - return Vendor.query() - .orderBy('displayName') - .onBuild((query) => { - if (!isEmpty(vendorsIds)) { - query.whereIn('id', vendorsIds); - } - }); - } - - getPayableAccounts(tenantId: number) { - const { Account } = this.tenancy.models(tenantId); - - return Account.query().where('accountType', ACCOUNT_TYPE.ACCOUNTS_PAYABLE); - } - - /** - * Retrieve - * @param tenantId - * @param asDate - * @returns - */ - async getReportVendorsTransactions(tenantId: number, asDate: Date | string) { - const { AccountTransaction } = this.tenancy.models(tenantId); - - // Retrieve payable accounts . - const payableAccounts = await this.getPayableAccounts(tenantId); - const payableAccountsIds = map(payableAccounts, 'id'); - - // Retrieve the customers transactions of A/R accounts. - const customersTranasctions = await AccountTransaction.query().onBuild( - (query) => { - query.whereIn('accountId', payableAccountsIds); - query.modify('filterDateRange', null, asDate); - query.groupBy('contactId'); - query.sum('credit as credit'); - query.sum('debit as debit'); - query.select('contactId'); - } + date: Date | string + ): Promise { + const transactions = await this.reportRepo.getVendorsTransactions( + tenantId, + date ); - const commonProps = { accountNormal: 'credit', date: asDate }; + const commonProps = { accountNormal: 'credit' }; - return R.map(R.merge(commonProps))(customersTranasctions); + return R.map(R.merge(commonProps))(transactions); } /** @@ -126,19 +93,21 @@ export default class VendorBalanceSummaryService } ); // Retrieve the vendors transactions. - const vendorsTransactions = await this.getReportVendorsTransactions( + const vendorsEntries = await this.getReportVendorsEntries( tenantId, query.asDate ); // Retrieve the customers list ordered by the display name. - const vendors = await this.getReportVendors(tenantId, query.vendorsIds); - + const vendors = await this.reportRepo.getVendors( + tenantId, + query.vendorsIds + ); // Ledger query. - const ledger = Ledger.fromTransactions(vendorsTransactions); + const vendorsLedger = new Ledger(vendorsEntries); // Report instance. const reportInstance = new VendorBalanceSummaryReport( - ledger, + vendorsLedger, vendors, filter, baseCurrency diff --git a/server/src/services/I18n/I18nService.ts b/server/src/services/I18n/I18nService.ts new file mode 100644 index 000000000..3f81343eb --- /dev/null +++ b/server/src/services/I18n/I18nService.ts @@ -0,0 +1,47 @@ +import HasTenancyService from 'services/Tenancy/TenancyService'; +import { Service, Inject } from 'typedi'; + +@Service() +export default class I18nService { + @Inject() + tenancy: HasTenancyService; + + /** + * + * @param i18n + * @param attributes + * @param data + * @returns + */ + private i18nAttributesMapper(i18n, attributes, data) { + return attributes.reduce((acc, attr, index) => { + return { + ...acc, + [attr]: i18n.__(acc[attr]), + }; + }, data); + } + + /** + * Mappes array collection to i18n localization based in given attributes. + * @param {Array} data - Array collection. + * @param {string[]} attributes - Attributes. + * @param {number} tenantId - Tenant id. + */ + public i18nMapper( + data: Array, + attributes: string[] = [], + tenantId: number + ) { + const i18n = this.tenancy.i18n(tenantId); + + return data.map((_data) => { + const newData = this.i18nAttributesMapper(i18n, attributes, _data); + + return { + ..._data, + ...newData, + }; + }); + } +} diff --git a/server/src/services/Inventory/Inventory.ts b/server/src/services/Inventory/Inventory.ts index 64b03fa15..22d909c09 100644 --- a/server/src/services/Inventory/Inventory.ts +++ b/server/src/services/Inventory/Inventory.ts @@ -37,6 +37,7 @@ export default class InventoryService { transformItemEntriesToInventory(transaction: { transactionId: number; transactionType: IItemEntryTransactionType; + transactionNumber?: string; date: Date | string; direction: TInventoryTransactionDirection; @@ -56,6 +57,10 @@ export default class InventoryService { entryId: entry.id, createdAt: transaction.createdAt, costAccountId: entry.costAccountId, + meta: { + transactionNumber: transaction.transactionNumber, + description: entry.description, + } })); } @@ -205,7 +210,7 @@ export default class InventoryService { inventoryEntry.transactionType ); } - return InventoryTransaction.query().insert({ + return InventoryTransaction.query().insertGraph({ ...inventoryEntry, }); } diff --git a/server/src/services/Inventory/InventoryAverageCost.ts b/server/src/services/Inventory/InventoryAverageCost.ts index ca7801dd6..b69613a91 100644 --- a/server/src/services/Inventory/InventoryAverageCost.ts +++ b/server/src/services/Inventory/InventoryAverageCost.ts @@ -165,9 +165,9 @@ export default class InventoryAverageCostMethod 'transactionId', 'transactionType', 'createdAt', - 'costAccountId', ]), + inventoryTransactionId: invTransaction.id, }; switch (invTransaction.direction) { case 'IN': diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index d8094ca0e..dfdf04290 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -536,6 +536,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService { const transaction = { transactionId: saleInvoice.id, transactionType: 'SaleInvoice', + transactionNumber: saleInvoice.invoiceNo, date: saleInvoice.invoiceDate, direction: 'OUT', diff --git a/server/src/utils/deepdash.ts b/server/src/utils/deepdash.ts new file mode 100644 index 000000000..b1e2f96f4 --- /dev/null +++ b/server/src/utils/deepdash.ts @@ -0,0 +1,52 @@ +import _ from 'lodash'; +import deepdash from 'deepdash'; + +const { + condense, + condenseDeep, + eachDeep, + exists, + filterDeep, + findDeep, + findPathDeep, + findValueDeep, + forEachDeep, + index, + keysDeep, + mapDeep, + mapKeysDeep, + mapValuesDeep, + omitDeep, + pathMatches, + pathToString, + paths, + pickDeep, + reduceDeep, + someDeep, + iteratee, +} = deepdash(_); + +export { + iteratee, + condense, + condenseDeep, + eachDeep, + exists, + filterDeep, + findDeep, + findPathDeep, + findValueDeep, + forEachDeep, + index, + keysDeep, + mapDeep, + mapKeysDeep, + mapValuesDeep, + omitDeep, + pathMatches, + pathToString, + paths, + pickDeep, + reduceDeep, + someDeep, +}; diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index 9d8fb0edd..a5f894765 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -51,6 +51,30 @@ const dateRangeCollection = ( return collection; }; +const dateRangeFromToCollection = ( + fromDate, + toDate, + addType = 'day', + increment = 1 +) => { + const collection = []; + const momentFromDate = moment(fromDate); + const dateFormat = 'YYYY-MM-DD'; + + for ( + let i = momentFromDate; + i.isBefore(toDate, addType) || i.isSame(toDate, addType); + i.add(increment, `${addType}s`) + ) { + collection.push({ + fromDate: i.startOf(addType).format(dateFormat), + toDate: i.endOf(addType).format(dateFormat), + }); + } + return collection; +}; + + const dateRangeFormat = (rangeType) => { switch (rangeType) { case 'year': @@ -329,8 +353,28 @@ var increment = (n) => { }; }; +const transformToMapBy = (collection, key) => { + return new Map( + Object.entries(_.groupBy(collection, key)), + ); +} + +const transformToMapKeyValue = (collection, key) => { + return new Map( + collection.map((item) => [item[key], item]), + ); +}; + + +const accumSum = (data, callback) => { + return data.reduce((acc, _data) => { + const amount = callback(_data); + return acc + amount; + }, 0) +} export { + accumSum, increment, hashPassword, origin, @@ -354,4 +398,7 @@ export { defaultToTransform, transformToMap, transactionIncrement, + transformToMapBy, + dateRangeFromToCollection, + transformToMapKeyValue };