fix: FastField re-rendering.

fix: Allocate landed cost dialog.
This commit is contained in:
a.bouhuolia
2021-07-26 19:45:16 +02:00
parent 77d987ef1f
commit 9baf81f803
77 changed files with 1046 additions and 364 deletions

View File

@@ -67,4 +67,4 @@ export const useManualJournalsColumns = () => {
],
[],
);
};
};

View File

@@ -3,16 +3,28 @@ import { FastField } from 'formik';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import MakeJournalEntriesTable from './MakeJournalEntriesTable';
import { defaultEntry, MIN_LINES_NUMBER } from './utils';
import { entriesFieldShouldUpdate, defaultEntry, MIN_LINES_NUMBER } from './utils';
import { useMakeJournalFormContext } from './MakeJournalProvider';
/**
* Make journal entries field.
*/
export default function MakeJournalEntriesField() {
const { accounts, contacts } = useMakeJournalFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
{({ form:{values ,setFieldValue}, field: { value }, meta: { error, touched } }) => (
<FastField
name={'entries'}
contacts={contacts}
accounts={accounts}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<MakeJournalEntriesTable
onChange={(entries) => {
setFieldValue('entries', entries);

View File

@@ -29,7 +29,10 @@ import {
import withSettings from 'containers/Settings/withSettings';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { useObserveJournalNoSettings } from './utils';
import {
currenciesFieldShouldUpdate,
useObserveJournalNoSettings,
} from './utils';
/**
* Make journal entries header.
*/
@@ -182,7 +185,11 @@ function MakeJournalEntriesHeader({
</FastField>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
<FastField
name={'currency_code'}
currencies={currencies}
shouldUpdate={currenciesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { sumBy, setWith, toSafeInteger, get, values } from 'lodash';
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
import moment from 'moment';
import {
transactionNumber,
updateTableRow,
repeatValue,
transformToForm,
defaultFastFieldShouldUpdate,
} from 'utils';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
@@ -123,17 +123,17 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
setEntriesErrors(error.indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) {
if (error.meta.find(meta => meta.contact_type === 'customer')) {
if (error.meta.find((meta) => meta.contact_type === 'customer')) {
toastMessages.push(
intl.get('receivable_accounts_should_assign_with_customers'),
);
}
if (error.meta.find(meta => meta.contact_type === 'vendor')) {
if (error.meta.find((meta) => meta.contact_type === 'vendor')) {
toastMessages.push(
intl.get('payable_accounts_should_assign_with_vendors'),
);
}
const indexes = error.meta.map((meta => meta.indexes)).flat();
const indexes = error.meta.map((meta) => meta.indexes).flat();
setEntriesErrors(indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
@@ -159,7 +159,28 @@ export const useObserveJournalNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {
const journalNo = transactionNumber(prefix, nextNumber);
const journalNo = transactionNumber(prefix, nextNumber);
setFieldValue('journal_number', journalNo);
}, [setFieldValue, prefix, nextNumber]);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.contacts !== oldProps.contacts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines currencies fast field should update.
*/
export const currenciesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.currencies !== oldProps.currencies ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -56,7 +56,7 @@ function BillTransactionDeleteAlert({
onConfirm={handleConfirmLandedCostDelete}
loading={isLoading}
>
<p>{/* <T id={''}/> */}</p>
<p><T id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}/></p>
</Alert>
);
}

View File

@@ -3,6 +3,7 @@ import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import moment from 'moment';
import { sumBy } from 'lodash';
import 'style/pages/AllocateLandedCost/AllocateLandedCostForm.scss';
@@ -48,6 +49,7 @@ function AllocateLandedCostForm({
cost: '',
})),
};
const amount = sumBy(initialValues.items, 'amount');
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
@@ -84,9 +86,12 @@ function AllocateLandedCostForm({
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
};
// Computed validation schema.
const validationSchema = AllocateLandedCostFormSchema(amount);
return (
<Formik
validationSchema={AllocateLandedCostFormSchema}
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={AllocateLandedCostFormContent}

View File

@@ -1,19 +1,18 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
const Schema = Yup.object().shape({
transaction_type: Yup.string().label(intl.get('transaction_type')),
transaction_date: Yup.date().label(intl.get('transaction_date')),
transaction_id: Yup.string().label(intl.get('transaction_number')),
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
amount: Yup.number().label(intl.get('amount')),
allocation_method: Yup.string().trim(),
items: Yup.array().of(
Yup.object().shape({
entry_id: Yup.number().nullable(),
cost: Yup.number().nullable(),
}),
),
});
export const AllocateLandedCostFormSchema = Schema;
export const AllocateLandedCostFormSchema = (minAmount) =>
Yup.object().shape({
transaction_type: Yup.string().label(intl.get('transaction_type')),
transaction_date: Yup.date().label(intl.get('transaction_date')),
transaction_id: Yup.string().label(intl.get('transaction_number')),
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
amount: Yup.number().max(minAmount).label(intl.get('amount')),
allocation_method: Yup.string().trim(),
items: Yup.array().of(
Yup.object().shape({
entry_id: Yup.number().nullable(),
cost: Yup.number().nullable(),
}),
),
});

View File

@@ -17,7 +17,7 @@ import allocateLandedCostType from 'common/allocateLandedCostType';
import { useLandedCostTransaction } from 'hooks/query';
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
import { getEntriesByTransactionId } from './utils';
import { getEntriesByTransactionId, allocateCostToEntries } from './utils';
/**
* Allocate landed cost form fields.
@@ -30,10 +30,10 @@ export default function AllocateLandedCostFormFields() {
} = useLandedCostTransaction(values.transaction_type);
// Retrieve entries of the given transaction id.
const transactionEntries = React.useMemo(() => getEntriesByTransactionId(
transactions,
values.transaction_id,
), [transactions, values.transaction_id]);
const transactionEntries = React.useMemo(
() => getEntriesByTransactionId(transactions, values.transaction_id),
[transactions, values.transaction_id],
);
return (
<div className={Classes.DIALOG_BODY}>
@@ -56,6 +56,8 @@ export default function AllocateLandedCostFormFields() {
items={allocateLandedCostType}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
setFieldValue('transaction_id', '');
setFieldValue('transaction_entry_id', '');
}}
filterable={false}
selectedItem={value}
@@ -82,13 +84,14 @@ export default function AllocateLandedCostFormFields() {
items={transactions}
onItemSelect={({ id }) => {
form.setFieldValue('transaction_id', id);
form.setFieldValue('transaction_entry_id', '');
}}
filterable={false}
selectedItem={value}
selectedItemProp={'id'}
textProp={'name'}
labelProp={'id'}
defaultText={intl.get('select_transaction')}
defaultText={intl.get('Select transaction')}
popoverProps={{ minimal: true }}
/>
</FormGroup>
@@ -112,15 +115,21 @@ export default function AllocateLandedCostFormFields() {
<ListSelect
items={transactionEntries}
onItemSelect={({ id, amount }) => {
form.setFieldValue('amount', amount)
const { items, allocation_method } = form.values;
form.setFieldValue('amount', amount);
form.setFieldValue('transaction_entry_id', id);
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'id'}
textProp={'name'}
labelProp={'id'}
defaultText={intl.get('select_transaction')}
defaultText={intl.get('Select transaction entry')}
popoverProps={{ minimal: true }}
/>
</FormGroup>
@@ -138,13 +147,24 @@ export default function AllocateLandedCostFormFields() {
className={'form-group--amount'}
inline={true}
>
<InputGroup {...field} />
<InputGroup
{...field}
onBlur={(e) => {
const amount = e.target.value;
const { allocation_method, items } = form.values;
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Allocation method -----------*/}
<FastField name={'allocation_method'}>
<Field name={'allocation_method'}>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
medium={true}
@@ -157,7 +177,13 @@ export default function AllocateLandedCostFormFields() {
>
<RadioGroup
onChange={handleStringChange((_value) => {
const { amount, items, allocation_method } = form.values;
form.setFieldValue('allocation_method', _value);
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
})}
selectedValue={value}
inline={true}
@@ -167,7 +193,7 @@ export default function AllocateLandedCostFormFields() {
</RadioGroup>
</FormGroup>
)}
</FastField>
</Field>
{/*------------ Allocate Landed cost Table -----------*/}
<AllocateLandedCostFormBody />

View File

@@ -1,3 +1,5 @@
import { sumBy, round } from 'lodash';
import * as R from 'ramda';
/**
* Retrieve transaction entries of the given transaction id.
*/
@@ -5,3 +7,56 @@ export function getEntriesByTransactionId(transactions, id) {
const transaction = transactions.find((trans) => trans.id === id);
return transaction ? transaction.entries : [];
}
export function allocateCostToEntries(total, allocateType, entries) {
return R.compose(
R.when(
R.always(allocateType === 'value'),
R.curry(allocateCostByValue)(total),
),
R.when(
R.always(allocateType === 'quantity'),
R.curry(allocateCostByQuantity)(total),
),
)(entries);
}
/**
* Allocate total cost on entries on value.
* @param {*} entries
* @param {*} total
* @returns
*/
export function allocateCostByValue(total, entries) {
const totalAmount = sumBy(entries, 'amount');
const _entries = entries.map((entry) => ({
...entry,
percentageOfValue: entry.amount / totalAmount,
}));
return _entries.map((entry) => ({
...entry,
cost: round(entry.percentageOfValue * total, 2),
}));
}
/**
* Allocate total cost on entries by quantity.
* @param {*} entries
* @param {*} total
* @returns
*/
export function allocateCostByQuantity(total, entries) {
const totalQuantity = sumBy(entries, 'quantity');
const _entries = entries.map((entry) => ({
...entry,
percentageOfQuantity: entry.quantity / totalQuantity,
}));
return _entries.map((entry) => ({
...entry,
cost: round(entry.percentageOfQuantity * total, 2),
}));
}

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/AccountDrawer.scss';
import { AccountDrawerProvider } from './AccountDrawerProvider';
import AccountDrawerDetails from './AccountDrawerDetails';

View File

@@ -5,8 +5,6 @@ import AccountDrawerHeader from './AccountDrawerHeader';
import AccountDrawerTable from './AccountDrawerTable';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import 'style/components/Drawer/AccountDrawer.scss';
/**
* Account view details.
*/

View File

@@ -1,5 +1,5 @@
import React from 'react';
import BillTransactionDeleteAlert from 'containers/Alerts/Bills/BillTransactionDeleteAlert';
import BillLocatedLandedCostDeleteAlert from 'containers/Alerts/Bills/BillLocatedLandedCostDeleteAlert';
/**
* Bill drawer alert.
@@ -7,7 +7,7 @@ import BillTransactionDeleteAlert from 'containers/Alerts/Bills/BillTransactionD
export default function BillDrawerAlerts() {
return (
<div class="bills-alerts">
<BillTransactionDeleteAlert name="transaction-delete" />
<BillLocatedLandedCostDeleteAlert name="bill-located-cost-delete" />
</div>
);
}

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/BillDrawer.scss';
import { BillDrawerProvider } from './BillDrawerProvider';
import BillDrawerDetails from './BillDrawerDetails';
import BillDrawerAlerts from './BillDrawerAlerts';

View File

@@ -4,8 +4,6 @@ import intl from 'react-intl-universal';
import LocatedLandedCostTable from './LocatedLandedCostTable';
import 'style/components/Drawer/BillDrawer.scss';
/**
* Bill view details.
*/

View File

@@ -18,7 +18,9 @@ function BillDrawerProvider({ billId, ...props }) {
//provider.
const provider = {
transactions,
billId,
};
return (
<DashboardInsider loading={isLandedCostLoading}>
<DrawerHeaderContent

View File

@@ -1,11 +1,17 @@
import React from 'react';
import { DataTable } from 'components';
import { DataTable, Card } from 'components';
import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
/**
* Located landed cost table.
@@ -13,25 +19,73 @@ import { compose } from 'utils';
function LocatedLandedCostTable({
// #withAlertsActions
openAlert,
// #withDialogActions
openDialog,
// #withDrawerActions
openDrawer,
}) {
const columns = useLocatedLandedCostColumns();
const { transactions } = useBillDrawerContext();
const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => {
openAlert('transaction-delete', { BillId: id });
openAlert('bill-located-cost-delete', { BillId: id });
};
// Handle allocate landed cost button click.
const handleAllocateCostClick = () => {
openDialog('allocate-landed-cost', { billId });
};
// Handle from transaction link click.
const handleFromTransactionClick = (original) => {
const { from_transaction_type, from_transaction_id } = original;
switch (from_transaction_type) {
case 'Expense':
openDrawer('expense-drawer', { expenseId: from_transaction_id });
break;
case 'Bill':
default:
openDrawer('bill-drawer', { billId: from_transaction_id });
break;
}
};
return (
<DataTable
columns={columns}
data={transactions}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteTransaction,
}}
/>
<div>
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="receipt-24" />}
text={'Allocate landed cost'}
onClick={handleAllocateCostClick}
/>
</NavbarGroup>
</DashboardActionsBar>
<Card>
<DataTable
columns={columns}
data={transactions}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteTransaction,
onFromTranscationClick: handleFromTransactionClick,
}}
className={'datatable--landed-cost-transactions'}
/>
</Card>
</div>
);
}
export default compose(withAlertsActions)(LocatedLandedCostTable);
export default compose(
withAlertsActions,
withDialogActions,
withDrawerActions,
)(LocatedLandedCostTable);

View File

@@ -3,7 +3,6 @@ import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Icon } from 'components';
/**
* Actions menu.
*/
@@ -20,22 +19,57 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
);
}
export function useLocatedLandedCostColumns() {
return React.useMemo(() => [
{
Header: intl.get('name'),
accessor: 'description',
width: 150,
},
{
Header: intl.get('amount'),
accessor: 'amount',
width: 100,
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method',
width: 100,
},
]);
/**
* From transaction table cell.
*/
export function FromTransactionCell({
row: { original },
payload: { onFromTranscationClick }
}) {
// Handle the link click
const handleAnchorClick = () => {
onFromTranscationClick && onFromTranscationClick(original);
};
return (
<a href="#" onClick={handleAnchorClick}>
{original.from_transaction_type} {original.from_transaction_id}
</a>
);
}
/**
* Retrieve bill located landed cost table columns.
*/
export function useLocatedLandedCostColumns() {
return React.useMemo(
() => [
{
Header: intl.get('name'),
accessor: 'description',
width: 150,
className: 'name',
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
width: 100,
className: 'amount',
},
{
id: 'from_transaction',
Header: intl.get('From transaction'),
Cell: FromTransactionCell,
width: 100,
className: 'from-transaction',
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method_formatted',
width: 100,
className: 'allocation-method',
},
],
[],
);
}

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/ViewDetails.scss';
import { ExpenseDrawerProvider } from './ExpenseDrawerProvider';
import ExpenseDrawerDetails from './ExpenseDrawerDetails';

View File

@@ -4,7 +4,6 @@ import ExpenseDrawerHeader from './ExpenseDrawerHeader';
import ExpenseDrawerTable from './ExpenseDrawerTable';
import ExpenseDrawerFooter from './ExpenseDrawerFooter';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import 'style/components/Drawer/ViewDetails.scss';
/**
* Expense view details.

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/ViewDetails.scss';
import { ManualJournalDrawerProvider } from './ManualJournalDrawerProvider';
import ManualJournalDrawerDetails from './ManualJournalDrawerDetails';

View File

@@ -6,8 +6,6 @@ import ManualJournalDrawerFooter from './ManualJournalDrawerFooter';
import { useManualJournalDrawerContext } from 'containers/Drawers/ManualJournalDrawer/ManualJournalDrawerProvider';
import 'style/components/Drawer/ViewDetails.scss';
/**
* Manual journal view details.
*/

View File

@@ -1,7 +1,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useJournal } from 'hooks/query';
import { DashboardInsider, DrawerHeaderContent } from 'components';
import intl from 'react-intl-universal';
const ManualJournalDrawerContext = React.createContext();

View File

@@ -69,6 +69,7 @@ export default function ManualJournalDrawerTable({
return (
<div className="journal-drawer__content--table">
<DataTable columns={columns} data={entries} />
<If condition={description}>
<p className={'desc'}>
<b>Description</b>: {description}

View File

@@ -1,11 +1,13 @@
import React from 'react';
import 'style/components/Drawers/DrawerTemplate.scss';
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 = {

View File

@@ -1,9 +1,11 @@
import React from 'react';
import 'style/components/Drawers/DrawerTemplate.scss';
import PaymentPaperTemplateHeader from './PaymentPaperTemplateHeader';
import PaymentPaperTemplateTable from './PaymentPaperTemplateTable';
import intl from 'react-intl-universal';
import 'style/components/Drawer/DrawerTemplate.scss';
export default function PaymentPaperTemplate({
labels: propLabels,

View File

@@ -30,6 +30,7 @@ function ItemsEntriesTable({
linesNumber,
currencyCode,
itemType, // sellable or purchasable
landedCost = false
}) {
const [rows, setRows] = React.useState(initialEntries);
const [rowItem, setRowItem] = React.useState(null);
@@ -94,7 +95,7 @@ function ItemsEntriesTable({
}, [entries, rows]);
// Editiable items entries columns.
const columns = useEditableItemsEntriesColumns();
const columns = useEditableItemsEntriesColumns({ landedCost });
// Handles the editor data update.
const handleUpdateData = useCallback(

View File

@@ -10,6 +10,7 @@ import {
ItemsListCell,
PercentFieldCell,
NumericInputCell,
CheckBoxFieldCell,
} from 'components/DataTableCells';
/**
@@ -90,27 +91,18 @@ export function IndexTableCell({ row: { index } }) {
return <span>{index + 1}</span>;
}
/**
* Landed cost cell.
*/
const LandedCostCell = ({
row: { index },
column: { id },
cell: { value: initialValue },
data,
payload,
}) => {
return <Checkbox minimal={true} className="ml2" />;
};
/**
* Landed cost header cell.
*/
const LandedCostHeaderCell = () => {
return (
<>
<T id={'cost'} />
<Hint content={''} />
<T id={'Landed'} />
<Hint
content={
'This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.'
}
/>
</>
);
};
@@ -118,7 +110,7 @@ const LandedCostHeaderCell = () => {
/**
* Retrieve editable items entries columns.
*/
export function useEditableItemsEntriesColumns() {
export function useEditableItemsEntriesColumns({ landedCost }) {
return React.useMemo(
() => [
{
@@ -182,14 +174,19 @@ export function useEditableItemsEntriesColumns() {
width: 100,
className: 'total',
},
{
Header: '',
accessor: 'landed_cost',
Cell: LandedCostCell,
width: 70,
disableSortBy: true,
disableResizing: true,
},
...(landedCost
? [
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
width: 100,
disableSortBy: true,
disableResizing: true,
className: 'landed-cost',
},
]
: []),
{
Header: '',
accessor: 'action',

View File

@@ -1,14 +1,22 @@
import { FastField } from 'formik';
import React from 'react';
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
import { defaultExpenseEntry } from './utils';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { defaultExpenseEntry, accountsFieldShouldUpdate } from './utils';
/**
* Expense form entries field.
*/
export default function ExpenseFormEntriesField({ linesNumber = 4 }) {
// Expense form context.
const { accounts } = useExpenseFormContext();
return (
<FastField name={'categories'}>
<FastField
name={'categories'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -22,12 +22,13 @@ export default function ExpenseFormEntriesTable({
error,
onChange,
currencyCode,
landedCost = true,
}) {
// Expense form context.
const { accounts } = useExpenseFormContext();
// Memorized data table columns.
const columns = useExpenseFormTableColumns();
const columns = useExpenseFormTableColumns({ landedCost });
// Handles update datatable data.
const handleUpdateData = useCallback(
@@ -61,6 +62,7 @@ export default function ExpenseFormEntriesTable({
return (
<DataTableEditable
name={'expense-form'}
columns={columns}
data={entries}
sticky={true}

View File

@@ -11,6 +11,7 @@ import {
inputIntent,
handleDateChange,
} from 'utils';
import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
import {
CurrencySelectList,
ContactSelecetList,
@@ -51,7 +52,11 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<FastField name={'payment_account_id'}>
<FastField
name={'payment_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}
@@ -118,7 +123,11 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer'} />}

View File

@@ -56,8 +56,12 @@ const ActionsCellRenderer = ({
const LandedCostHeaderCell = () => {
return (
<>
<T id={'cost'} />
<Hint content={''} />
<T id={'Landed'} />
<Hint
content={
'This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.'
}
/>
</>
);
};
@@ -87,7 +91,7 @@ function ExpenseAccountFooterCell() {
/**
* Retrieve expense form table entries columns.
*/
export function useExpenseFormTableColumns() {
export function useExpenseFormTableColumns({ landedCost }) {
return React.useMemo(
() => [
{
@@ -127,15 +131,19 @@ export function useExpenseFormTableColumns() {
className: 'description',
width: 100,
},
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
disableSortBy: true,
disableResizing: true,
width: 70,
className: 'landed_cost',
},
...(landedCost
? [
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
disableSortBy: true,
disableResizing: true,
width: 100,
className: 'landed-cost',
},
]
: []),
{
Header: '',
accessor: 'action',

View File

@@ -1,7 +1,11 @@
import { AppToaster } from 'components';
import moment from 'moment';
import intl from 'react-intl-universal';
import { transformToForm, repeatValue } from 'utils';
import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
@@ -27,7 +31,7 @@ export const defaultExpenseEntry = {
amount: '',
expense_account_id: '',
description: '',
landed_cost: 0,
landed_cost: false,
};
export const defaultExpense = {
@@ -62,3 +66,23 @@ export const transformToEditForm = (
],
};
};
/**
* Detarmine cusotmers fast-field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmine accounts fast-field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { useFormikContext, FastField, Field, ErrorMessage } from 'formik';
import {
FormGroup,
Classes,
@@ -23,12 +23,21 @@ import withSettings from 'containers/Settings/withSettings';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { compose, inputIntent } from 'utils';
import {
sellDescriptionFieldShouldUpdate,
sellAccountFieldShouldUpdate,
sellPriceFieldShouldUpdate,
costPriceFieldShouldUpdate,
costAccountFieldShouldUpdate,
purchaseDescFieldShouldUpdate,
} from './utils';
/**
* Item form body.
*/
function ItemFormBody({ baseCurrency }) {
const { accounts } = useItemFormContext();
const { values } = useFormikContext();
return (
<div class="page-form__section page-form__section--selling-cost">
@@ -53,7 +62,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Selling price ------------- */}
<FastField name={'sell_price'}>
<FastField
name={'sell_price'}
sellable={values.sellable}
shouldUpdate={sellPriceFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'selling_price'} />}
@@ -78,7 +91,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Selling account ------------- */}
<FastField name={'sell_account_id'}>
<FastField
name={'sell_account_id'}
sellable={values.sellable}
accounts={accounts}
shouldUpdate={sellAccountFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
@@ -107,7 +125,11 @@ function ItemFormBody({ baseCurrency }) {
)}
</FastField>
<FastField name={'sell_description'}>
<FastField
name={'sell_description'}
sellable={values.sellable}
shouldUpdate={sellDescriptionFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
@@ -146,7 +168,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Cost price ------------- */}
<FastField name={'cost_price'}>
<FastField
name={'cost_price'}
purchasable={values.purchasable}
shouldUpdate={costPriceFieldShouldUpdate}
>
{({ field, form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cost_price'} />}
@@ -171,7 +197,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Cost account ------------- */}
<FastField name={'cost_account_id'}>
<FastField
name={'cost_account_id'}
purchasable={values.purchasable}
accounts={accounts}
shouldUpdate={costAccountFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
@@ -200,7 +231,11 @@ function ItemFormBody({ baseCurrency }) {
)}
</FastField>
<FastField name={'purchase_description'}>
<FastField
name={'purchase_description'}
purchasable={values.purchasable}
shouldUpdate={purchaseDescFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}

View File

@@ -8,6 +8,7 @@ import classNames from 'classnames';
import withSettings from 'containers/Settings/withSettings';
import { accountsFieldShouldUpdate } from './utils';
import { compose, inputIntent } from 'utils';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useItemFormContext } from './ItemFormProvider';
@@ -27,7 +28,11 @@ function ItemFormInventorySection({ baseCurrency }) {
<Row>
<Col xs={6}>
{/*------------- Inventory account ------------- */}
<FastField name={'inventory_account_id'}>
<FastField
name={'inventory_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
label={<T id={'inventory_account'} />}

View File

@@ -21,6 +21,7 @@ import { CLASSES } from 'common/classes';
import { useItemFormContext } from './ItemFormProvider';
import { handleStringChange, inputIntent } from 'utils';
import { categoriesFieldShouldUpdate } from './utils';
/**
* Item form primary section.
@@ -130,7 +131,11 @@ export default function ItemFormPrimarySection() {
</FastField>
{/*----------- Item category ----------*/}
<FastField name={'category_id'}>
<FastField
name={'category_id'}
categories={itemsCategories}
shouldUpdate={categoriesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'category'} />}

View File

@@ -1,6 +1,7 @@
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { defaultFastFieldShouldUpdate } from 'utils';
export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
const table = {
@@ -28,7 +29,9 @@ export const handleDeleteErrors = (errors) => {
)
) {
AppToaster.show({
message: intl.get('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,
});
}
@@ -38,8 +41,83 @@ export const handleDeleteErrors = (errors) => {
)
) {
AppToaster.show({
message: intl.get('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,
});
}
};
/**
* Detarmines accounts fast field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines categories fast field should update.
*/
export const categoriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.categories !== oldProps.categories ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell price fast field should update.
*/
export const sellPriceFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell account fast field should update.
*/
export const sellAccountFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell description fast field should update.
*/
export const sellDescriptionFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const costAccountFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const costPriceFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const purchaseDescFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -48,7 +48,7 @@ function BillForm({
currency_code: baseCurrency,
}),
}),
[bill],
[bill, baseCurrency],
);
// Transform response error to fields.

View File

@@ -7,6 +7,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { ContactSelecetList, FieldRequiredHint, Icon } from 'components';
import { vendorsFieldShouldUpdate } from './utils';
import { useBillFormContext } from './BillFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -28,7 +29,11 @@ function BillFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------- Vendor name ------ */}
<FastField name={'vendor_id'}>
<FastField
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'vendor_name'} />}

View File

@@ -4,13 +4,23 @@ import { FastField } from 'formik';
import { CLASSES } from 'common/classes';
import { useBillFormContext } from './BillFormProvider';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import {
entriesFieldShouldUpdate
} from './utils';
/**
* Bill form body.
*/
export default function BillFormBody({ defaultBill }) {
const { items } = useBillFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
@@ -25,6 +35,7 @@ export default function BillFormBody({ defaultBill }) {
errors={error}
linesNumber={4}
currencyCode={values.currency_code}
landedCost={true}
/>
)}
</FastField>

View File

@@ -2,7 +2,11 @@ import moment from 'moment';
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { transformToForm, repeatValue } from 'utils';
import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -13,6 +17,7 @@ export const defaultBillEntry = {
discount: '',
quantity: '',
description: '',
landed_cost: false,
};
export const defaultBill = {
@@ -62,3 +67,23 @@ export const handleDeleteErrors = (errors) => {
});
}
};
/**
* Detarmines vendors fast field should update
*/
export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.vendors !== oldProps.vendors ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -59,7 +59,7 @@ export function ActionsMenu({
/>
</If>
<MenuItem
// icon={<Icon icon="quick-payment-16" iconSize={16} />}
icon={<Icon icon="receipt-24" iconSize={16} />}
text={intl.get('allocate_landed_coast')}
onClick={safeCallback(onAllocateLandedCost, original)}
/>

View File

@@ -36,6 +36,7 @@ import {
fullAmountPaymentEntries,
amountPaymentEntries,
} from 'utils';
import { accountsFieldShouldUpdate, vendorsFieldShouldUpdate } from './utils';
/**
* Payment made form header fields.
@@ -48,17 +49,14 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
} = useFormikContext();
// Payment made form context.
const {
vendors,
accounts,
isNewMode,
setPaymentVendorId,
} = usePaymentMadeFormContext();
const { vendors, accounts, isNewMode, setPaymentVendorId } =
usePaymentMadeFormContext();
// Sumation of payable full-amount.
const payableFullAmount = useMemo(() => safeSumBy(entries, 'due_amount'), [
entries,
]);
const payableFullAmount = useMemo(
() => safeSumBy(entries, 'due_amount'),
[entries],
);
// Handle receive full-amount click.
const handleReceiveFullAmountClick = () => {
@@ -78,7 +76,11 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------ Vendor name ------------ */}
<FastField name={'vendor_id'}>
<FastField
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'vendor_name'} />}
@@ -157,7 +159,7 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
small={true}
minimal={true}
>
<T id={'receive_full_amount'} /> (
<T id={'receive_full_amount'} /> (
<Money amount={payableFullAmount} currency={baseCurrency} />)
</Button>
</FormGroup>
@@ -184,7 +186,11 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
</FastField>
{/* ------------ Payment account ------------ */}
<FastField name={'payment_account_id'}>
<FastField
name={'payment_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}

View File

@@ -1,5 +1,9 @@
import moment from 'moment';
import { safeSumBy, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
safeSumBy,
transformToForm,
} from 'utils';
export const ERRORS = {
PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE',
@@ -9,10 +13,10 @@ export const ERRORS = {
export const defaultPaymentMadeEntry = {
bill_id: '',
payment_amount: '',
currency_code:'',
currency_code: '',
id: null,
due_amount: null,
amount:''
amount: '',
};
// Default initial values of payment made.
@@ -48,7 +52,26 @@ export const transformToNewPageEntries = (entries) => {
return entries.map((entry) => ({
...transformToForm(entry, defaultPaymentMadeEntry),
payment_amount: '',
currency_code:entry.currency_code,
currency_code: entry.currency_code,
}));
}
};
/**
* Detarmines vendors fast field when update.
*/
export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.vendors !== oldProps.vendors ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines accounts fast field when update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -27,6 +27,7 @@ function EstimateFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<EstimateFormHeaderFields />
<PageFormBigNumber
label={intl.get('amount')}
amount={totalDueAmount}

View File

@@ -15,6 +15,7 @@ import {
inputIntent,
handleDateChange,
} from 'utils';
import { customersFieldShouldUpdate } from './utils';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import {
@@ -67,7 +68,11 @@ function EstimateFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -170,7 +175,9 @@ function EstimateFormHeader({
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_estimate_number'}/>,
content: (
<T id={'setting_your_auto_generated_estimate_number'} />
),
position: Position.BOTTOM_LEFT,
}}
/>

View File

@@ -4,6 +4,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useEstimateFormContext } from './EstimateFormProvider';
import { entriesFieldShouldUpdate } from './utils';
/**
* Estimate form items entries editor.
@@ -13,7 +14,11 @@ export default function EstimateFormItemsEntriesField() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -49,4 +54,24 @@ export const useObserveEstimateNoSettings = (prefix, nextNumber) => {
const estimateNo = transactionNumber(prefix, nextNumber);
setFieldValue('estimate_number', estimateNo);
}, [setFieldValue, prefix, nextNumber]);
}
};
/**
* Detarmines customers fast field when update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -10,7 +10,10 @@ import { FastField, Field, ErrorMessage } from 'formik';
import { FormattedMessage as T } from 'components';
import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames';
import { useObserveInvoiceNoSettings } from './utils';
import {
useObserveInvoiceNoSettings,
customerNameFieldShouldUpdate,
} from './utils';
import { CLASSES } from 'common/classes';
import {
ContactSelecetList,
@@ -58,15 +61,16 @@ function InvoiceFormHeaderFields({
};
// Syncs invoice number settings with form.
useObserveInvoiceNoSettings(
invoiceNumberPrefix,
invoiceNextNumber,
);
useObserveInvoiceNoSettings(invoiceNumberPrefix, invoiceNextNumber);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -168,7 +172,9 @@ function InvoiceFormHeaderFields({
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_invoice_number'}/>,
content: (
<T id={'setting_your_auto_generated_invoice_number'} />
),
position: Position.BOTTOM_LEFT,
}}
/>

View File

@@ -4,6 +4,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { entriesFieldShouldUpdate } from './utils';
/**
* Invoice items entries editor field.
@@ -13,7 +14,11 @@ export default function InvoiceItemsEntriesEditorField() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -11,7 +11,7 @@ import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import { useFormikContext } from 'formik';
import { Intent } from '@blueprintjs/core';
import { orderingLinesIndexes } from 'utils';
import { defaultFastFieldShouldUpdate } from 'utils';
import intl from 'react-intl-universal';
import { ERROR } from 'common/errors';
import { AppToaster } from 'components';
@@ -100,4 +100,18 @@ export const useObserveInvoiceNoSettings = (prefix, nextNumber) => {
const invoiceNo = transactionNumber(prefix, nextNumber);
setFieldValue('invoice_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]);
};
};
export const customerNameFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -34,6 +34,7 @@ import {
} from 'components';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings';
@@ -41,6 +42,8 @@ import {
useObservePaymentNoSettings,
amountPaymentEntries,
fullAmountPaymentEntries,
customersFieldShouldUpdate,
accountsFieldShouldUpdate,
} from './utils';
import { toSafeInteger } from 'lodash';
@@ -115,7 +118,11 @@ function PaymentReceiveHeaderFields({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------- Customer name ------------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -247,7 +254,11 @@ function PaymentReceiveHeaderFields({
</FastField>
{/* ------------ Deposit account ------------ */}
<FastField name={'deposit_account_id'}>
<FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_to'} />}

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, transformToForm, safeSumBy } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
transformToForm,
safeSumBy,
} from 'utils';
// Default payment receive entry.
export const defaultPaymentReceiveEntry = {
@@ -99,3 +104,23 @@ export const useObservePaymentNoSettings = (prefix, nextNumber) => {
setFieldValue('payment_receive_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]);
};
/**
* Detarmines the customers fast-field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines the accounts fast-field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -28,7 +28,11 @@ import {
inputIntent,
} from 'utils';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { useObserveReceiptNoSettings } from './utils';
import {
accountsFieldShouldUpdate,
customersFieldShouldUpdate,
useObserveReceiptNoSettings,
} from './utils';
/**
* Receipt form header fields.
@@ -70,7 +74,11 @@ function ReceiptFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -94,7 +102,11 @@ function ReceiptFormHeader({
</FastField>
{/* ----------- Deposit account ----------- */}
<FastField name={'deposit_account_id'}>
<FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_account'} />}

View File

@@ -4,13 +4,14 @@ import { FastField } from 'formik';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { CLASSES } from 'common/classes';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { entriesFieldShouldUpdate } from './utils';
export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
const { items } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField name={'entries'} items={items} shouldUpdate={entriesFieldShouldUpdate}>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -42,7 +47,6 @@ export const transformToEditForm = (receipt) => ({
],
});
export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
@@ -50,4 +54,34 @@ export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const receiptNo = transactionNumber(prefix, nextNumber);
setFieldValue('receipt_number', receiptNo);
}, [setFieldValue, prefix, nextNumber]);
}
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines accounts fast field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines customers fast field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};