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

@@ -0,0 +1,5 @@
import React from 'react';
export default function Card({ children }) {
return <div class="card">{children}</div>;
}

View File

@@ -4,14 +4,26 @@ import { CLASSES } from 'common/classes';
import { DataTable, If } from 'components'; import { DataTable, If } from 'components';
import 'style/components/DataTable/DataTableEditable.scss'; import 'style/components/DataTable/DataTableEditable.scss';
/**
* Editable datatable.
*/
export default function DatatableEditable({ export default function DatatableEditable({
totalRow = false, totalRow = false,
actions, actions,
name,
className, className,
...tableProps ...tableProps
}) { }) {
return ( return (
<div className={classNames(CLASSES.DATATABLE_EDITOR, className)}> <div
className={classNames(
CLASSES.DATATABLE_EDITOR,
{
[`${CLASSES.DATATABLE_EDITOR}--${name}`]: name,
},
className,
)}
>
<DataTable {...tableProps} /> <DataTable {...tableProps} />
<If condition={actions}> <If condition={actions}>

View File

@@ -0,0 +1,29 @@
import React from 'react';
import className from 'classname';
/**
* Details menu.
*/
export function DetailsMenu({ children, vertical = false }) {
return (
<div
className={className('details-menu', {
'is-vertical': vertical,
})}
>
{children}
</div>
);
}
/**
* Detail item.
*/
export function DetailItem({ label, children }) {
return (
<div class="detail-item">
<div class="detail-item__label">{label}</div>
<div class="detail-item__content">{children}</div>
</div>
);
}

View File

@@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import { Position, Drawer } from '@blueprintjs/core'; import { Position, Drawer } from '@blueprintjs/core';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import 'style/components/Drawer.scss';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils'; import { compose } from 'utils';
/**
* Drawer component.
*/
function DrawerComponent(props) { function DrawerComponent(props) {
const { name, children, onClose, closeDrawer } = props; const { name, children, onClose, closeDrawer } = props;

View File

@@ -57,6 +57,7 @@ import Postbox from './Postbox';
import AccountsSuggestField from './AccountsSuggestField'; import AccountsSuggestField from './AccountsSuggestField';
import MaterialProgressBar from './MaterialProgressBar'; import MaterialProgressBar from './MaterialProgressBar';
import { MoneyFieldCell } from './DataTableCells'; import { MoneyFieldCell } from './DataTableCells';
import Card from './Card';
import { ItemsMultiSelect } from './Items'; import { ItemsMultiSelect } from './Items';
@@ -127,5 +128,6 @@ export {
AccountsSuggestField, AccountsSuggestField,
MaterialProgressBar, MaterialProgressBar,
MoneyFieldCell, MoneyFieldCell,
ItemsMultiSelect ItemsMultiSelect,
Card
}; };

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { Intent } from '@blueprintjs/core'; 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 moment from 'moment';
import { import {
transactionNumber, transactionNumber,
updateTableRow, updateTableRow,
repeatValue, repeatValue,
transformToForm, transformToForm,
defaultFastFieldShouldUpdate,
} from 'utils'; } from 'utils';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
@@ -123,17 +123,17 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
setEntriesErrors(error.indexes, 'contact_id', 'error'); setEntriesErrors(error.indexes, 'contact_id', 'error');
} }
if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) { 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( toastMessages.push(
intl.get('receivable_accounts_should_assign_with_customers'), 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( toastMessages.push(
intl.get('payable_accounts_should_assign_with_vendors'), 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'); setEntriesErrors(indexes, 'contact_id', 'error');
} }
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) { if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
@@ -163,3 +163,24 @@ export const useObserveJournalNoSettings = (prefix, nextNumber) => {
setFieldValue('journal_number', journalNo); setFieldValue('journal_number', journalNo);
}, [setFieldValue, prefix, nextNumber]); }, [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} onConfirm={handleConfirmLandedCostDelete}
loading={isLoading} 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> </Alert>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,17 @@
import React from 'react'; 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 { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider'; import { useBillDrawerContext } from './BillDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions'; import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils'; import { compose } from 'utils';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
/** /**
* Located landed cost table. * Located landed cost table.
@@ -13,25 +19,73 @@ import { compose } from 'utils';
function LocatedLandedCostTable({ function LocatedLandedCostTable({
// #withAlertsActions // #withAlertsActions
openAlert, openAlert,
// #withDialogActions
openDialog,
// #withDrawerActions
openDrawer,
}) { }) {
const columns = useLocatedLandedCostColumns(); const columns = useLocatedLandedCostColumns();
const { transactions } = useBillDrawerContext(); const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action. // Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => { 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 ( return (
<div>
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="receipt-24" />}
text={'Allocate landed cost'}
onClick={handleAllocateCostClick}
/>
</NavbarGroup>
</DashboardActionsBar>
<Card>
<DataTable <DataTable
columns={columns} columns={columns}
data={transactions} data={transactions}
ContextMenu={ActionsMenu} ContextMenu={ActionsMenu}
payload={{ payload={{
onDelete: handleDeleteTransaction, 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 { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils'; import { safeCallback } from 'utils';
import { Icon } from 'components'; import { Icon } from 'components';
/** /**
* Actions menu. * Actions menu.
*/ */
@@ -20,22 +19,57 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
); );
} }
/**
* 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() { export function useLocatedLandedCostColumns() {
return React.useMemo(() => [ return React.useMemo(
() => [
{ {
Header: intl.get('name'), Header: intl.get('name'),
accessor: 'description', accessor: 'description',
width: 150, width: 150,
className: 'name',
}, },
{ {
Header: intl.get('amount'), Header: intl.get('amount'),
accessor: 'amount', accessor: 'formatted_amount',
width: 100, width: 100,
className: 'amount',
},
{
id: 'from_transaction',
Header: intl.get('From transaction'),
Cell: FromTransactionCell,
width: 100,
className: 'from-transaction',
}, },
{ {
Header: intl.get('allocation_method'), Header: intl.get('allocation_method'),
accessor: 'allocation_method', accessor: 'allocation_method_formatted',
width: 100, width: 100,
className: 'allocation-method',
}, },
]); ],
[],
);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import 'style/components/Drawers/DrawerTemplate.scss';
import PaperTemplateHeader from './PaperTemplateHeader'; import PaperTemplateHeader from './PaperTemplateHeader';
import PaperTemplateTable from './PaperTemplateTable'; import PaperTemplateTable from './PaperTemplateTable';
import PaperTemplateFooter from './PaperTemplateFooter'; import PaperTemplateFooter from './PaperTemplateFooter';
import { updateItemsEntriesTotal } from 'containers/Entries/utils'; import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import 'style/components/Drawer/DrawerTemplate.scss';
function PaperTemplate({ labels: propLabels, paperData, entries }) { function PaperTemplate({ labels: propLabels, paperData, entries }) {
const labels = { const labels = {

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import {
ItemsListCell, ItemsListCell,
PercentFieldCell, PercentFieldCell,
NumericInputCell, NumericInputCell,
CheckBoxFieldCell,
} from 'components/DataTableCells'; } from 'components/DataTableCells';
/** /**
@@ -90,27 +91,18 @@ export function IndexTableCell({ row: { index } }) {
return <span>{index + 1}</span>; 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. * Landed cost header cell.
*/ */
const LandedCostHeaderCell = () => { const LandedCostHeaderCell = () => {
return ( return (
<> <>
<T id={'cost'} /> <T id={'Landed'} />
<Hint content={''} /> <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. * Retrieve editable items entries columns.
*/ */
export function useEditableItemsEntriesColumns() { export function useEditableItemsEntriesColumns({ landedCost }) {
return React.useMemo( return React.useMemo(
() => [ () => [
{ {
@@ -182,14 +174,19 @@ export function useEditableItemsEntriesColumns() {
width: 100, width: 100,
className: 'total', className: 'total',
}, },
...(landedCost
? [
{ {
Header: '', Header: LandedCostHeaderCell,
accessor: 'landed_cost', accessor: 'landed_cost',
Cell: LandedCostCell, Cell: CheckBoxFieldCell,
width: 70, width: 100,
disableSortBy: true, disableSortBy: true,
disableResizing: true, disableResizing: true,
className: 'landed-cost',
}, },
]
: []),
{ {
Header: '', Header: '',
accessor: 'action', accessor: 'action',

View File

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

View File

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

View File

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

View File

@@ -56,8 +56,12 @@ const ActionsCellRenderer = ({
const LandedCostHeaderCell = () => { const LandedCostHeaderCell = () => {
return ( return (
<> <>
<T id={'cost'} /> <T id={'Landed'} />
<Hint content={''} /> <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. * Retrieve expense form table entries columns.
*/ */
export function useExpenseFormTableColumns() { export function useExpenseFormTableColumns({ landedCost }) {
return React.useMemo( return React.useMemo(
() => [ () => [
{ {
@@ -127,15 +131,19 @@ export function useExpenseFormTableColumns() {
className: 'description', className: 'description',
width: 100, width: 100,
}, },
...(landedCost
? [
{ {
Header: LandedCostHeaderCell, Header: LandedCostHeaderCell,
accessor: 'landed_cost', accessor: 'landed_cost',
Cell: CheckBoxFieldCell, Cell: CheckBoxFieldCell,
disableSortBy: true, disableSortBy: true,
disableResizing: true, disableResizing: true,
width: 70, width: 100,
className: 'landed_cost', className: 'landed-cost',
}, },
]
: []),
{ {
Header: '', Header: '',
accessor: 'action', accessor: 'action',

View File

@@ -1,7 +1,11 @@
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import moment from 'moment'; import moment from 'moment';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { transformToForm, repeatValue } from 'utils'; import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
const ERROR = { const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED', EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
@@ -27,7 +31,7 @@ export const defaultExpenseEntry = {
amount: '', amount: '',
expense_account_id: '', expense_account_id: '',
description: '', description: '',
landed_cost: 0, landed_cost: false,
}; };
export const defaultExpense = { 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 React from 'react';
import { FastField, Field, ErrorMessage } from 'formik'; import { useFormikContext, FastField, Field, ErrorMessage } from 'formik';
import { import {
FormGroup, FormGroup,
Classes, Classes,
@@ -23,12 +23,21 @@ import withSettings from 'containers/Settings/withSettings';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes'; import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { compose, inputIntent } from 'utils'; import { compose, inputIntent } from 'utils';
import {
sellDescriptionFieldShouldUpdate,
sellAccountFieldShouldUpdate,
sellPriceFieldShouldUpdate,
costPriceFieldShouldUpdate,
costAccountFieldShouldUpdate,
purchaseDescFieldShouldUpdate,
} from './utils';
/** /**
* Item form body. * Item form body.
*/ */
function ItemFormBody({ baseCurrency }) { function ItemFormBody({ baseCurrency }) {
const { accounts } = useItemFormContext(); const { accounts } = useItemFormContext();
const { values } = useFormikContext();
return ( return (
<div class="page-form__section page-form__section--selling-cost"> <div class="page-form__section page-form__section--selling-cost">
@@ -53,7 +62,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField> </FastField>
{/*------------- Selling price ------------- */} {/*------------- Selling price ------------- */}
<FastField name={'sell_price'}> <FastField
name={'sell_price'}
sellable={values.sellable}
shouldUpdate={sellPriceFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'selling_price'} />} label={<T id={'selling_price'} />}
@@ -78,7 +91,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField> </FastField>
{/*------------- Selling account ------------- */} {/*------------- 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 } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'account'} />} label={<T id={'account'} />}
@@ -107,7 +125,11 @@ function ItemFormBody({ baseCurrency }) {
)} )}
</FastField> </FastField>
<FastField name={'sell_description'}> <FastField
name={'sell_description'}
sellable={values.sellable}
shouldUpdate={sellDescriptionFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => ( {({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'description'} />} label={<T id={'description'} />}
@@ -146,7 +168,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField> </FastField>
{/*------------- Cost price ------------- */} {/*------------- Cost price ------------- */}
<FastField name={'cost_price'}> <FastField
name={'cost_price'}
purchasable={values.purchasable}
shouldUpdate={costPriceFieldShouldUpdate}
>
{({ field, form, field: { value }, meta: { error, touched } }) => ( {({ field, form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'cost_price'} />} label={<T id={'cost_price'} />}
@@ -171,7 +197,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField> </FastField>
{/*------------- Cost account ------------- */} {/*------------- 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 } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'account'} />} label={<T id={'account'} />}
@@ -200,7 +231,11 @@ function ItemFormBody({ baseCurrency }) {
)} )}
</FastField> </FastField>
<FastField name={'purchase_description'}> <FastField
name={'purchase_description'}
purchasable={values.purchasable}
shouldUpdate={purchaseDescFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => ( {({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'description'} />} label={<T id={'description'} />}

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { defaultFastFieldShouldUpdate } from 'utils';
export const transitionItemTypeKeyToLabel = (itemTypeKey) => { export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
const table = { const table = {
@@ -28,7 +29,9 @@ export const handleDeleteErrors = (errors) => {
) )
) { ) {
AppToaster.show({ 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, intent: Intent.DANGER,
}); });
} }
@@ -38,8 +41,83 @@ export const handleDeleteErrors = (errors) => {
) )
) { ) {
AppToaster.show({ 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, 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, currency_code: baseCurrency,
}), }),
}), }),
[bill], [bill, baseCurrency],
); );
// Transform response error to fields. // Transform response error to fields.

View File

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

View File

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

View File

@@ -2,7 +2,11 @@ import moment from 'moment';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { transformToForm, repeatValue } from 'utils'; import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
export const MIN_LINES_NUMBER = 4; export const MIN_LINES_NUMBER = 4;
@@ -13,6 +17,7 @@ export const defaultBillEntry = {
discount: '', discount: '',
quantity: '', quantity: '',
description: '', description: '',
landed_cost: false,
}; };
export const defaultBill = { 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> </If>
<MenuItem <MenuItem
// icon={<Icon icon="quick-payment-16" iconSize={16} />} icon={<Icon icon="receipt-24" iconSize={16} />}
text={intl.get('allocate_landed_coast')} text={intl.get('allocate_landed_coast')}
onClick={safeCallback(onAllocateLandedCost, original)} onClick={safeCallback(onAllocateLandedCost, original)}
/> />

View File

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

View File

@@ -1,5 +1,9 @@
import moment from 'moment'; import moment from 'moment';
import { safeSumBy, transformToForm } from 'utils'; import {
defaultFastFieldShouldUpdate,
safeSumBy,
transformToForm,
} from 'utils';
export const ERRORS = { export const ERRORS = {
PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE', PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE',
@@ -9,10 +13,10 @@ export const ERRORS = {
export const defaultPaymentMadeEntry = { export const defaultPaymentMadeEntry = {
bill_id: '', bill_id: '',
payment_amount: '', payment_amount: '',
currency_code:'', currency_code: '',
id: null, id: null,
due_amount: null, due_amount: null,
amount:'' amount: '',
}; };
// Default initial values of payment made. // Default initial values of payment made.
@@ -48,7 +52,26 @@ export const transformToNewPageEntries = (entries) => {
return entries.map((entry) => ({ return entries.map((entry) => ({
...transformToForm(entry, defaultPaymentMadeEntry), ...transformToForm(entry, defaultPaymentMadeEntry),
payment_amount: '', 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 ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<EstimateFormHeaderFields /> <EstimateFormHeaderFields />
<PageFormBigNumber <PageFormBigNumber
label={intl.get('amount')} label={intl.get('amount')}
amount={totalDueAmount} amount={totalDueAmount}

View File

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

View File

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

View File

@@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils'; import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4; export const MIN_LINES_NUMBER = 4;
@@ -49,4 +54,24 @@ export const useObserveEstimateNoSettings = (prefix, nextNumber) => {
const estimateNo = transactionNumber(prefix, nextNumber); const estimateNo = transactionNumber(prefix, nextNumber);
setFieldValue('estimate_number', estimateNo); setFieldValue('estimate_number', estimateNo);
}, [setFieldValue, prefix, nextNumber]); }, [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 { FormattedMessage as T } from 'components';
import { momentFormatter, compose, tansformDateValue } from 'utils'; import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames'; import classNames from 'classnames';
import { useObserveInvoiceNoSettings } from './utils'; import {
useObserveInvoiceNoSettings,
customerNameFieldShouldUpdate,
} from './utils';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { import {
ContactSelecetList, ContactSelecetList,
@@ -58,15 +61,16 @@ function InvoiceFormHeaderFields({
}; };
// Syncs invoice number settings with form. // Syncs invoice number settings with form.
useObserveInvoiceNoSettings( useObserveInvoiceNoSettings(invoiceNumberPrefix, invoiceNextNumber);
invoiceNumberPrefix,
invoiceNextNumber,
);
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}> <FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'customer_name'} />} label={<T id={'customer_name'} />}
@@ -168,7 +172,9 @@ function InvoiceFormHeaderFields({
}} }}
tooltip={true} tooltip={true}
tooltipProps={{ tooltipProps={{
content: <T id={'setting_your_auto_generated_invoice_number'}/>, content: (
<T id={'setting_your_auto_generated_invoice_number'} />
),
position: Position.BOTTOM_LEFT, position: Position.BOTTOM_LEFT,
}} }}
/> />

View File

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

View File

@@ -11,7 +11,7 @@ import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { orderingLinesIndexes } from 'utils'; import { defaultFastFieldShouldUpdate } from 'utils';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { ERROR } from 'common/errors'; import { ERROR } from 'common/errors';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
@@ -101,3 +101,17 @@ export const useObserveInvoiceNoSettings = (prefix, nextNumber) => {
setFieldValue('invoice_no', invoiceNo); setFieldValue('invoice_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]); }, [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'; } from 'components';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { ACCOUNT_TYPE } from 'common/accountTypes'; import { ACCOUNT_TYPE } from 'common/accountTypes';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
@@ -41,6 +42,8 @@ import {
useObservePaymentNoSettings, useObservePaymentNoSettings,
amountPaymentEntries, amountPaymentEntries,
fullAmountPaymentEntries, fullAmountPaymentEntries,
customersFieldShouldUpdate,
accountsFieldShouldUpdate,
} from './utils'; } from './utils';
import { toSafeInteger } from 'lodash'; import { toSafeInteger } from 'lodash';
@@ -115,7 +118,11 @@ function PaymentReceiveHeaderFields({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------- Customer name ------------- */} {/* ------------- Customer name ------------- */}
<FastField name={'customer_id'}> <FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'customer_name'} />} label={<T id={'customer_name'} />}
@@ -247,7 +254,11 @@ function PaymentReceiveHeaderFields({
</FastField> </FastField>
{/* ------------ Deposit account ------------ */} {/* ------------ Deposit account ------------ */}
<FastField name={'deposit_account_id'}> <FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'deposit_to'} />} label={<T id={'deposit_to'} />}

View File

@@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { transactionNumber, transformToForm, safeSumBy } from 'utils'; import {
defaultFastFieldShouldUpdate,
transactionNumber,
transformToForm,
safeSumBy,
} from 'utils';
// Default payment receive entry. // Default payment receive entry.
export const defaultPaymentReceiveEntry = { export const defaultPaymentReceiveEntry = {
@@ -99,3 +104,23 @@ export const useObservePaymentNoSettings = (prefix, nextNumber) => {
setFieldValue('payment_receive_no', invoiceNo); setFieldValue('payment_receive_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]); }, [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, inputIntent,
} from 'utils'; } from 'utils';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import { useObserveReceiptNoSettings } from './utils'; import {
accountsFieldShouldUpdate,
customersFieldShouldUpdate,
useObserveReceiptNoSettings,
} from './utils';
/** /**
* Receipt form header fields. * Receipt form header fields.
@@ -70,7 +74,11 @@ function ReceiptFormHeader({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}> <FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'customer_name'} />} label={<T id={'customer_name'} />}
@@ -94,7 +102,11 @@ function ReceiptFormHeader({
</FastField> </FastField>
{/* ----------- Deposit account ----------- */} {/* ----------- Deposit account ----------- */}
<FastField name={'deposit_account_id'}> <FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'deposit_account'} />} label={<T id={'deposit_account'} />}

View File

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

View File

@@ -1,7 +1,12 @@
import React from 'react'; import React from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils'; import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4; export const MIN_LINES_NUMBER = 4;
@@ -42,7 +47,6 @@ export const transformToEditForm = (receipt) => ({
], ],
}); });
export const useObserveReceiptNoSettings = (prefix, nextNumber) => { export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
@@ -50,4 +54,34 @@ export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const receiptNo = transactionNumber(prefix, nextNumber); const receiptNo = transactionNumber(prefix, nextNumber);
setFieldValue('receipt_number', receiptNo); setFieldValue('receipt_number', receiptNo);
}, [setFieldValue, prefix, nextNumber]); }, [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)
);
};

View File

@@ -1146,5 +1146,11 @@
"No items": "No items", "No items": "No items",
"cannot_delete_bill_that_has_associated_landed_cost_transactions": "Cannot delete bill that has associated landed cost transactions.", "cannot_delete_bill_that_has_associated_landed_cost_transactions": "Cannot delete bill that has associated landed cost transactions.",
"couldn_t_delete_expense_transaction_has_associated_located_landed_cost_transaction": "Couldn't delete expense transaction has associated located landed cost transaction", "couldn_t_delete_expense_transaction_has_associated_located_landed_cost_transaction": "Couldn't delete expense transaction has associated located landed cost transaction",
"the_landed_cost_has_been_created_successfully": "The landed cost has been created successfully" "the_landed_cost_has_been_created_successfully": "The landed cost has been created successfully",
"Select transaction": "Select transaction",
"Select transaction entry": "Select transaction entry",
"From transaction": "From transaction",
"Landed": "Landed",
"This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.": "This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.",
"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?": "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?"
} }

View File

@@ -10,7 +10,7 @@
.th, .th,
.td { .td {
border-left: 1px dashed #e2e2e2; border-left: 1px solid #e2e2e2;
&.index { &.index {
text-align: center; text-align: center;
@@ -55,6 +55,19 @@
margin-bottom: auto; margin-bottom: auto;
} }
} }
&.landed-cost{
.bp3-control{
margin-top: 0;
margin-left: 34px;
}
.bp3-control-indicator{
height: 18px;
width: 18px;
border-color: #e0e0e0;
}
}
} }
.tr { .tr {
.bp3-form-group:not(.bp3-intent-danger) .bp3-input, .bp3-form-group:not(.bp3-intent-danger) .bp3-input,

View File

@@ -0,0 +1,21 @@
.details-menu {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
&.is-vertical {}
.detail-item {
&__label {
color: #666666;
font-weight: 500;
}
&__content {
text-transform: capitalize;
margin: 5px 0;
}
}
}

View File

@@ -0,0 +1,17 @@
.bp3-drawer {
.bp3-drawer-header {
margin-bottom: 2px;
background-color: #FFF;
.bp3-heading {
font-weight: 500;
}
.bp3-heading,
.bp3-icon {
color: #354152;
}
}
}

View File

@@ -1,14 +1,4 @@
.bp3-drawer-header {
box-shadow: 0 0 0;
.bp3-heading{
font-size: 16px;
}
.bp3-button{
min-height: 28px;
min-width: 28px;
}
}
.account-drawer { .account-drawer {
background-color: #fbfbfb; background-color: #fbfbfb;
@@ -95,28 +85,3 @@
} }
} }
} }
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}

View File

@@ -4,6 +4,7 @@
.bp3-tabs { .bp3-tabs {
.bp3-tab-list { .bp3-tab-list {
position: relative; position: relative;
background-color: #FFF;
&:before { &:before {
content: ''; content: '';
@@ -11,7 +12,7 @@
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 2px; height: 2px;
background: #f0f0f0; background: #e1e2e8;
} }
> *:not(:last-child) { > *:not(:last-child) {
@@ -29,14 +30,18 @@
} }
} }
} }
.bp3-tab-panel{
margin-top: 0;
.card{
margin: 15px;
}
}
} }
.bigcapital-datatable { .datatable--landed-cost-transactions {
.table { .table {
max-height: 500px;
border: 1px solid #d1dee2;
min-width: auto;
margin: 12px;
.tbody, .tbody,
.tbody-inner { .tbody-inner {
@@ -48,34 +53,13 @@
} }
.tbody { .tbody {
.tr .td { .tr .td {
padding: 0.8rem; padding: 0.6rem;
&.amount{
font-weight: 600;
}
} }
} }
} }
} }
} }
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}

View File

@@ -122,6 +122,7 @@
top: 0; top: 0;
overflow: auto; overflow: auto;
height: 100%; height: 100%;
.bp3-drawer-header .bp3-heading { .bp3-drawer-header .bp3-heading {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@@ -1,6 +1,5 @@
.journal-drawer, .journal-drawer,
.expense-drawer { .expense-drawer {
background: #f5f5f5;
&__content { &__content {
display: flex; display: flex;
@@ -18,8 +17,8 @@
justify-content: flex-start; justify-content: flex-start;
margin: 15px 0 20px; margin: 15px 0 20px;
font-size: 14px; font-size: 14px;
// color: #333333;
color: #666666; color: #666666;
> div { > div {
flex-grow: 1; flex-grow: 1;
span { span {
@@ -44,17 +43,17 @@
&--table { &--table {
flex-grow: 1; flex-grow: 1;
flex-shrink: 0; flex-shrink: 0;
.table { .table {
color: #666666; color: #666666;
font-size: 14px; font-size: 14px;
.thead .tr .th .resizer {
display: none;
}
.thead .th { .thead .th {
background: transparent;
color: #222222; color: #222222;
border-bottom: 1px solid #000000; border-bottom: 1px solid #000000;
padding: 0.5rem;
} }
.thead .th,
.tbody .tr .td { .tbody .tr .td {
background: transparent; background: transparent;
padding: 0.8rem 0.5rem; padding: 0.8rem 0.5rem;
@@ -63,7 +62,6 @@
.desc { .desc {
margin: 20px 0 60px; margin: 20px 0 60px;
// margin: 20px 0;
> b { > b {
color: #2f2f2f; color: #2f2f2f;
} }
@@ -93,25 +91,3 @@
} }
} }
} }
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}

View File

@@ -11,7 +11,6 @@ body.page-bill-edit{
padding-bottom: 64px; padding-bottom: 64px;
} }
.page-form--bill{ .page-form--bill{
$self: '.page-form'; $self: '.page-form';

View File

@@ -1,11 +1,10 @@
.dashboard__insider--expenses {
.dashboard__insider--expenses{ .bigcapital-datatable {
.bigcapital-datatable{ .tbody {
.tr .td.total_amount {
.tbody{ span {
.tr .td.total_amount{
span{
font-weight: 600; font-weight: 600;
} }
} }
@@ -13,36 +12,64 @@
} }
} }
.page-form--expense{ .page-form--expense {
$self: '.page-form'; $self: '.page-form';
#{$self}__header{ #{$self}__header {
display: flex; display: flex;
&-fields{ &-fields {
flex: 1 0 0; flex: 1 0 0;
} }
.bp3-label{ .bp3-label {
min-width: 140px; min-width: 140px;
} }
.bp3-form-content{
.bp3-form-content {
width: 100%; width: 100%;
} }
.bp3-form-group{ .bp3-form-group {
margin-bottom: 18px; margin-bottom: 18px;
&.bp3-inline{ &.bp3-inline {
max-width: 440px; max-width: 440px;
} }
} }
} }
.form-group--description{ .datatable-editor--expense-form {
.table {
.tbody {
.tr .td {
&.landed-cost {
.bp3-control {
margin-top: 0;
margin-left: 34px;
}
.bp3-control-indicator {
height: 18px;
width: 18px;
border-color: #e0e0e0;
}
}
}
}
}
}
.form-group--description {
max-width: 500px; max-width: 500px;
textarea{ textarea {
min-height: 60px; min-height: 60px;
width: 100%; width: 100%;
} }

View File

@@ -1,3 +1,7 @@
// Noto Sans
// -------------------------------------
@font-face { @font-face {
font-family: Noto Sans; font-family: Noto Sans;
src: local('Noto Sans'), url('../fonts/NotoSans-SemiBold.woff') format('woff'); src: local('Noto Sans'), url('../fonts/NotoSans-SemiBold.woff') format('woff');
@@ -30,46 +34,8 @@
font-display: swap; font-display: swap;
} }
// arabic regular // Segoe UI Arabic
@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 // Segoe UI Arabic - Regular
@font-face { @font-face {
font-family: 'Segoe UI'; font-family: 'Segoe UI';

View File

@@ -16,7 +16,7 @@ $menu-item-color-active: $light-gray3;
$breadcrumbs-collapsed-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#6B8193' enable-background='new 0 0 16 16' xml:space='preserve'><g><circle cx='2' cy='8.03' r='2'/><circle cx='14' cy='8.03' r='2'/><circle cx='8' cy='8.03' r='2'/></g></svg>"); $breadcrumbs-collapsed-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#6B8193' enable-background='new 0 0 16 16' xml:space='preserve'><g><circle cx='2' cy='8.03' r='2'/><circle cx='14' cy='8.03' r='2'/><circle cx='8' cy='8.03' r='2'/></g></svg>");
$sidebar-zindex: 15; $sidebar-zindex: 15;
$pt-font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, $pt-font-family: 'Noto Sans', -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue,
Icons16, sans-serif; Icons16, sans-serif;

View File

@@ -90,7 +90,9 @@ export const objectKeysTransform = (obj, transform) => {
export const compose = (...funcs) => export const compose = (...funcs) =>
funcs.reduce( funcs.reduce(
(a, b) => (...args) => a(b(...args)), (a, b) =>
(...args) =>
a(b(...args)),
(arg) => arg, (arg) => arg,
); );
@@ -639,7 +641,32 @@ const getCurrenciesOptions = () => {
currency_code: currencyCode, currency_code: currencyCode,
formatted_name: `${currencyCode} - ${currency.name}`, formatted_name: `${currencyCode} - ${currency.name}`,
}; };
}) });
} };
export const currenciesOptions = getCurrenciesOptions(); export const currenciesOptions = getCurrenciesOptions();
/**
* Deeply get a value from an object via its path.
*/
function getIn(obj, key, def, p = 0) {
const path = _.toPath(key);
while (obj && p < path.length) {
obj = obj[path[p++]];
}
return obj === undefined ? def : obj;
}
export const defaultFastFieldShouldUpdate = (props, prevProps) => {
return (
props.name !== prevProps.name ||
getIn(props.formik.values, prevProps.name) !==
getIn(prevProps.formik.values, prevProps.name) ||
getIn(props.formik.errors, prevProps.name) !==
getIn(prevProps.formik.errors, prevProps.name) ||
getIn(props.formik.touched, prevProps.name) !==
getIn(prevProps.formik.touched, prevProps.name) ||
Object.keys(prevProps).length !== Object.keys(props).length ||
props.formik.isSubmitting !== prevProps.formik.isSubmitting
);
};

View File

@@ -71,7 +71,6 @@ export default class CashFlowController extends BaseFinancialReportController {
/** /**
* Transformes the report statement to table rows. * Transformes the report statement to table rows.
* @param {ITransactionsByVendorsStatement} statement - * @param {ITransactionsByVendorsStatement} statement -
*
*/ */
private transformToTableRows(cashFlowDOO: ICashFlowStatementDOO, tenantId: number) { private transformToTableRows(cashFlowDOO: ICashFlowStatementDOO, tenantId: number) {
const i18n = this.tenancy.i18n(tenantId); const i18n = this.tenancy.i18n(tenantId);

View File

@@ -45,7 +45,7 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
* Transformes the report statement to table rows. * Transformes the report statement to table rows.
* @param {IVendorBalanceSummaryStatement} statement - * @param {IVendorBalanceSummaryStatement} statement -
*/ */
transformToTableRows({ data }: IVendorBalanceSummaryStatement) { private transformToTableRows({ data }: IVendorBalanceSummaryStatement) {
return { return {
table: { table: {
data: this.vendorBalanceSummaryTableRows.tableRowsTransformer(data), data: this.vendorBalanceSummaryTableRows.tableRowsTransformer(data),
@@ -57,7 +57,10 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
* Transformes the report statement to raw json. * Transformes the report statement to raw json.
* @param {IVendorBalanceSummaryStatement} statement - * @param {IVendorBalanceSummaryStatement} statement -
*/ */
transformToJsonResponse({ data, columns }: IVendorBalanceSummaryStatement) { private transformToJsonResponse({
data,
columns,
}: IVendorBalanceSummaryStatement) {
return { return {
data: this.transfromToResponse(data), data: this.transfromToResponse(data),
columns: this.transfromToResponse(columns), columns: this.transfromToResponse(columns),
@@ -76,7 +79,8 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR
const filter = this.matchedQueryData(req); const filter = this.matchedQueryData(req);
try { try {
const vendorBalanceSummary = await this.vendorBalanceSummaryService.vendorBalanceSummary( const vendorBalanceSummary =
await this.vendorBalanceSummaryService.vendorBalanceSummary(
tenantId, tenantId,
filter filter
); );

View File

@@ -192,7 +192,10 @@ export default class BillAllocateLandedCost extends BaseController {
billId billId
); );
return res.status(200).send({ billId, transactions }); return res.status(200).send({
billId,
transactions: this.transfromToResponse(transactions)
});
} catch (error) { } catch (error) {
next(error); next(error);
} }

View File

@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { ref } from 'objection'; import { ref, transaction } from 'objection';
import { import {
ILandedCostTransactionsQueryDTO, ILandedCostTransactionsQueryDTO,
ILandedCostTransaction, ILandedCostTransaction,
@@ -8,6 +8,7 @@ import {
import TransactionLandedCost from './TransctionLandedCost'; import TransactionLandedCost from './TransctionLandedCost';
import BillsService from '../Bills'; import BillsService from '../Bills';
import HasTenancyService from 'services/Tenancy/TenancyService'; import HasTenancyService from 'services/Tenancy/TenancyService';
import { formatNumber } from 'utils';
@Service() @Service()
export default class LandedCostListing { export default class LandedCostListing {
@@ -71,8 +72,15 @@ export default class LandedCostListing {
const landedCostTransactions = await BillLandedCost.query() const landedCostTransactions = await BillLandedCost.query()
.where('bill_id', billId) .where('bill_id', billId)
.withGraphFetched('allocateEntries'); .withGraphFetched('allocateEntries')
.withGraphFetched('bill');
return landedCostTransactions; return landedCostTransactions.map((transaction) => ({
...transaction.toJSON(),
formattedAmount: formatNumber(
transaction.amount,
transaction.bill.currencyCode
),
}));
}; };
} }