mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: add warehouse transfer & expenses & journal.
This commit is contained in:
@@ -9,7 +9,6 @@ import { FastField, ErrorMessage } from 'formik';
|
|||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Features } from 'common';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
@@ -24,14 +23,12 @@ import {
|
|||||||
FieldHint,
|
FieldHint,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
If,
|
|
||||||
FeatureCan,
|
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
CurrencySelectList,
|
CurrencySelectList,
|
||||||
ExchangeRateInputGroup,
|
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
import { JournalExchangeRateInputField } from './components';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import {
|
import {
|
||||||
currenciesFieldShouldUpdate,
|
currenciesFieldShouldUpdate,
|
||||||
@@ -52,13 +49,7 @@ function MakeJournalEntriesHeader({
|
|||||||
journalNextNumber,
|
journalNextNumber,
|
||||||
journalNumberPrefix,
|
journalNumberPrefix,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { currencies } = useMakeJournalFormContext();
|
||||||
currencies,
|
|
||||||
isForeignJournal,
|
|
||||||
baseCurrency,
|
|
||||||
selectJournalCurrency,
|
|
||||||
setSelactJournalCurrency,
|
|
||||||
} = useMakeJournalFormContext();
|
|
||||||
|
|
||||||
// Handle journal number change.
|
// Handle journal number change.
|
||||||
const handleJournalNumberChange = () => {
|
const handleJournalNumberChange = () => {
|
||||||
@@ -195,45 +186,35 @@ function MakeJournalEntriesHeader({
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/*------------ Currency -----------*/}
|
{/*------------ Currency -----------*/}
|
||||||
<FeatureCan feature={Features.ManualJournal}>
|
<FastField
|
||||||
<FastField
|
name={'currency_code'}
|
||||||
name={'currency_code'}
|
currencies={currencies}
|
||||||
currencies={currencies}
|
shouldUpdate={currenciesFieldShouldUpdate}
|
||||||
shouldUpdate={currenciesFieldShouldUpdate}
|
>
|
||||||
>
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
<FormGroup
|
||||||
<FormGroup
|
label={<T id={'currency'} />}
|
||||||
label={<T id={'currency'} />}
|
className={classNames('form-group--currency', CLASSES.FILL)}
|
||||||
className={classNames('form-group--currency', CLASSES.FILL)}
|
inline={true}
|
||||||
inline={true}
|
>
|
||||||
>
|
<CurrencySelectList
|
||||||
<CurrencySelectList
|
currenciesList={currencies}
|
||||||
currenciesList={currencies}
|
selectedCurrencyCode={value}
|
||||||
selectedCurrencyCode={value}
|
onCurrencySelected={(currencyItem) => {
|
||||||
onCurrencySelected={(currencyItem) => {
|
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||||
form.setFieldValue(
|
form.setFieldValue('exchange_rate', '');
|
||||||
'currency_code',
|
}}
|
||||||
currencyItem.currency_code,
|
defaultSelectText={value}
|
||||||
);
|
/>
|
||||||
form.setFieldValue('exchange_rate', '');
|
</FormGroup>
|
||||||
setSelactJournalCurrency(currencyItem);
|
)}
|
||||||
}}
|
</FastField>
|
||||||
defaultSelectText={value}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</FeatureCan>
|
|
||||||
|
|
||||||
{/* ----------- Exchange rate ----------- */}
|
{/* ----------- Exchange rate ----------- */}
|
||||||
<If condition={isForeignJournal}>
|
<JournalExchangeRateInputField
|
||||||
<ExchangeRateInputGroup
|
name={'exchange_rate'}
|
||||||
fromCurrency={baseCurrency}
|
formGroupProps={{ label: ' ', inline: true }}
|
||||||
toCurrency={selectJournalCurrency?.currency_code}
|
/>
|
||||||
name={'exchange_rate'}
|
|
||||||
formGroupProps={{ label: ' ', inline: true }}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField } from 'formik';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { FormGroup, TextArea } from '@blueprintjs/core';
|
import { Row, Col, Paper } from 'components';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { MakeJournalFormFooterLeft } from './MakeJournalFormFooterLeft';
|
||||||
import { Postbox, ErrorMessage, Row, Col } from 'components';
|
import { MakeJournalFormFooterRight } from './MakeJournalFormFooterRight';
|
||||||
import Dragzone from 'components/Dragzone';
|
|
||||||
import { inputIntent } from 'utils';
|
|
||||||
|
|
||||||
export default function MakeJournalFormFooter() {
|
export default function MakeJournalFormFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<Postbox title={<T id={'journal_details'} />} defaultOpen={false}>
|
<MakeJournalFooterPaper>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
<FastField name={'description'}>
|
<MakeJournalFormFooterLeft />
|
||||||
{({ field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--description'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="description" />}
|
|
||||||
fill={true}
|
|
||||||
>
|
|
||||||
<TextArea fill={true} {...field} />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Dragzone
|
<MakeJournalFormFooterRight />
|
||||||
initialFiles={[]}
|
|
||||||
// onDrop={handleDropFiles}
|
|
||||||
// onDeleteFile={handleDeleteFile}
|
|
||||||
hint={<T id={'attachments_maximum'} />}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Postbox>
|
</MakeJournalFooterPaper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const MakeJournalFooterPaper = styled(Paper)`
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
export function MakeJournalFormFooterLeft() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* --------- Description --------- */}
|
||||||
|
<DescriptionFormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
name={'description'}
|
||||||
|
>
|
||||||
|
<FEditableText
|
||||||
|
name={'description'}
|
||||||
|
placeholder={
|
||||||
|
'Enter the description of your business to be displayed in your transaction'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DescriptionFormGroup>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DescriptionFormGroup = styled(FFormGroup)`
|
||||||
|
&.bp3-form-group {
|
||||||
|
.bp3-label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.bp3-form-content {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
T,
|
||||||
|
TotalLines,
|
||||||
|
TotalLine,
|
||||||
|
TotalLineBorderStyle,
|
||||||
|
TotalLineTextStyle,
|
||||||
|
} from 'components';
|
||||||
|
import { useJournalTotals } from './utils';
|
||||||
|
|
||||||
|
export function MakeJournalFormFooterRight() {
|
||||||
|
const { formattedSubtotal, formattedTotal } = useJournalTotals();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MakeJouranlTotalLines>
|
||||||
|
<TotalLine
|
||||||
|
title={<T id={'manual_journal.details.subtotal'} />}
|
||||||
|
value={formattedSubtotal}
|
||||||
|
borderStyle={TotalLineBorderStyle.None}
|
||||||
|
/>
|
||||||
|
<TotalLine
|
||||||
|
title={<T id={'manual_journal.details.total'} />}
|
||||||
|
value={formattedTotal}
|
||||||
|
// borderStyle={TotalLineBorderStyle.SingleDark}
|
||||||
|
textStyle={TotalLineTextStyle.Bold}
|
||||||
|
/>
|
||||||
|
</MakeJouranlTotalLines>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MakeJouranlTotalLines = styled(TotalLines)`
|
||||||
|
width: 100%;
|
||||||
|
color: #555555;
|
||||||
|
`;
|
||||||
@@ -10,6 +10,10 @@ import {
|
|||||||
BranchesListFieldCell,
|
BranchesListFieldCell,
|
||||||
} from 'components/DataTableCells';
|
} from 'components/DataTableCells';
|
||||||
import { useFeatureCan } from 'hooks/state';
|
import { useFeatureCan } from 'hooks/state';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { ExchangeRateInputGroup } from 'components';
|
||||||
|
import { useCurrentOrganization } from 'hooks/state';
|
||||||
|
import { useJournalIsForeign } from './utils';
|
||||||
import { Features } from 'common';
|
import { Features } from 'common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,3 +149,26 @@ export const useJournalTableEntriesColumns = () => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Journal exchange rate input field.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export function JournalExchangeRateInputField({ ...props }) {
|
||||||
|
const currentOrganization = useCurrentOrganization();
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
const isForeignJouranl = useJournalIsForeign();
|
||||||
|
|
||||||
|
// Can't continue if the customer is not foreign.
|
||||||
|
if (!isForeignJouranl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ExchangeRateInputGroup
|
||||||
|
fromCurrency={values.currency_code}
|
||||||
|
toCurrency={currentOrganization.base_currency}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import {
|
|||||||
transformToForm,
|
transformToForm,
|
||||||
defaultFastFieldShouldUpdate,
|
defaultFastFieldShouldUpdate,
|
||||||
ensureEntriesHasEmptyLine,
|
ensureEntriesHasEmptyLine,
|
||||||
|
formattedAmount,
|
||||||
|
safeSumBy,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
import { useCurrentOrganization } from 'hooks/state';
|
||||||
|
|
||||||
const ERROR = {
|
const ERROR = {
|
||||||
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
|
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
|
||||||
@@ -214,3 +217,48 @@ export const useSetPrimaryBranchToForm = () => {
|
|||||||
}
|
}
|
||||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retreives the Journal totals.
|
||||||
|
*/
|
||||||
|
export const useJournalTotals = () => {
|
||||||
|
const {
|
||||||
|
values: { entries, currency_code: currencyCode },
|
||||||
|
} = useFormikContext();
|
||||||
|
|
||||||
|
// Retrieves the invoice entries total.
|
||||||
|
const totalCredit = safeSumBy(entries, 'credit');
|
||||||
|
const totalDebit = safeSumBy(entries, 'debit');
|
||||||
|
|
||||||
|
const total = Math.max(totalCredit, totalDebit);
|
||||||
|
// Retrieves the formatted total money.
|
||||||
|
const formattedTotal = React.useMemo(
|
||||||
|
() => formattedAmount(total, currencyCode),
|
||||||
|
[total, currencyCode],
|
||||||
|
);
|
||||||
|
// Retrieves the formatted subtotal.
|
||||||
|
const formattedSubtotal = React.useMemo(
|
||||||
|
() => formattedAmount(total, currencyCode, { money: false }),
|
||||||
|
[total, currencyCode],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedTotal,
|
||||||
|
formattedSubtotal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the expenses has foreign .
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const useJournalIsForeign = () => {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
const currentOrganization = useCurrentOrganization();
|
||||||
|
|
||||||
|
const isForeignJournal = React.useMemo(
|
||||||
|
() => values.currency_code !== currentOrganization.base_currency,
|
||||||
|
[values.currency_code, currentOrganization.base_currency],
|
||||||
|
);
|
||||||
|
return isForeignJournal;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,41 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField } from 'formik';
|
|
||||||
import { FormGroup, TextArea } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { inputIntent } from 'utils';
|
import styled from 'styled-components';
|
||||||
import { Row, Dragzone, Col, Postbox } from 'components';
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { Row, Col, Paper } from 'components';
|
||||||
|
import { ExpenseFormFooterLeft } from './ExpenseFormFooterLeft';
|
||||||
|
import { ExpenseFormFooterRight } from './ExpenseFormFooterRight';
|
||||||
|
|
||||||
export default function ExpenseFormFooter() {
|
export default function ExpenseFormFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<Postbox title={<T id={'expense_details'} />} defaultOpen={false}>
|
<ExpensesFooterPaper>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
<FastField name={'description'}>
|
<ExpenseFormFooterLeft />
|
||||||
{({ field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--description'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
>
|
|
||||||
<TextArea growVertically={true} {...field} />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Dragzone
|
<ExpenseFormFooterRight />
|
||||||
initialFiles={[]}
|
|
||||||
// onDrop={handleDropFiles}
|
|
||||||
// onDeleteFile={handleDeleteFile}
|
|
||||||
hint={<T id={'attachments_maximum'} />}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Postbox>
|
</ExpensesFooterPaper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ExpensesFooterPaper = styled(Paper)`
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|||||||
33
src/containers/Expenses/ExpenseForm/ExpenseFormFooterLeft.js
Normal file
33
src/containers/Expenses/ExpenseForm/ExpenseFormFooterLeft.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
export function ExpenseFormFooterLeft() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* --------- Description --------- */}
|
||||||
|
<DescriptionFormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
name={'description'}
|
||||||
|
>
|
||||||
|
<FEditableText
|
||||||
|
name={'description'}
|
||||||
|
placeholder={
|
||||||
|
'Enter the description of your business to be displayed in your transaction'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DescriptionFormGroup>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const DescriptionFormGroup = styled(FFormGroup)`
|
||||||
|
&.bp3-form-group {
|
||||||
|
.bp3-label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.bp3-form-content {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
T,
|
||||||
|
TotalLines,
|
||||||
|
TotalLine,
|
||||||
|
TotalLineBorderStyle,
|
||||||
|
TotalLineTextStyle,
|
||||||
|
} from 'components';
|
||||||
|
import { useExpensesTotals } from './utils';
|
||||||
|
|
||||||
|
export function ExpenseFormFooterRight() {
|
||||||
|
const { formattedSubtotal, formattedTotal } = useExpensesTotals();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExpensesTotalLines>
|
||||||
|
<TotalLine
|
||||||
|
title={<T id={'manual_journal.details.subtotal'} />}
|
||||||
|
value={formattedSubtotal}
|
||||||
|
borderStyle={TotalLineBorderStyle.None}
|
||||||
|
/>
|
||||||
|
<TotalLine
|
||||||
|
title={<T id={'manual_journal.details.total'} />}
|
||||||
|
value={formattedTotal}
|
||||||
|
// borderStyle={TotalLineBorderStyle.SingleDark}
|
||||||
|
textStyle={TotalLineTextStyle.Bold}
|
||||||
|
/>
|
||||||
|
</ExpensesTotalLines>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExpensesTotalLines = styled(TotalLines)`
|
||||||
|
width: 100%;
|
||||||
|
color: #555555;
|
||||||
|
`;
|
||||||
@@ -23,10 +23,9 @@ import {
|
|||||||
CustomerSelectField,
|
CustomerSelectField,
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
ExchangeRateInputGroup,
|
|
||||||
Hint,
|
Hint,
|
||||||
If,
|
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { ExpensesExchangeRateInputField } from './components';
|
||||||
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
|
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
|
||||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||||
|
|
||||||
@@ -34,15 +33,7 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
|||||||
* Expense form header.
|
* Expense form header.
|
||||||
*/
|
*/
|
||||||
export default function ExpenseFormHeader() {
|
export default function ExpenseFormHeader() {
|
||||||
const {
|
const { currencies, accounts, customers } = useExpenseFormContext();
|
||||||
currencies,
|
|
||||||
accounts,
|
|
||||||
customers,
|
|
||||||
isForeignCustomer,
|
|
||||||
baseCurrency,
|
|
||||||
selectCustomer,
|
|
||||||
setSelectCustomer,
|
|
||||||
} = useExpenseFormContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
|
||||||
@@ -118,7 +109,6 @@ export default function ExpenseFormHeader() {
|
|||||||
selectedCurrencyCode={value}
|
selectedCurrencyCode={value}
|
||||||
onCurrencySelected={(currencyItem) => {
|
onCurrencySelected={(currencyItem) => {
|
||||||
form.setFieldValue('currency_code', currencyItem.currency_code);
|
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||||
setSelectCustomer(currencyItem);
|
|
||||||
}}
|
}}
|
||||||
defaultSelectText={value}
|
defaultSelectText={value}
|
||||||
/>
|
/>
|
||||||
@@ -126,14 +116,11 @@ export default function ExpenseFormHeader() {
|
|||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
<If condition={isForeignCustomer}>
|
{/* ----------- Exchange rate ----------- */}
|
||||||
<ExchangeRateInputGroup
|
<ExpensesExchangeRateInputField
|
||||||
fromCurrency={baseCurrency}
|
name={'exchange_rate'}
|
||||||
toCurrency={selectCustomer?.currency_code}
|
formGroupProps={{ label: ' ', inline: true }}
|
||||||
name={'exchange_rate'}
|
/>
|
||||||
formGroupProps={{ label: ' ', inline: true }}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<FastField name={'reference_no'}>
|
<FastField name={'reference_no'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import {
|
|||||||
AccountsListFieldCell,
|
AccountsListFieldCell,
|
||||||
CheckBoxFieldCell,
|
CheckBoxFieldCell,
|
||||||
} from 'components/DataTableCells';
|
} from 'components/DataTableCells';
|
||||||
import { formattedAmount, safeSumBy } from 'utils';
|
import { useFormikContext } from 'formik';
|
||||||
|
import { ExchangeRateInputGroup } from 'components';
|
||||||
|
import { useCurrentOrganization } from 'hooks/state';
|
||||||
|
import { useExpensesIsForeign } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expense category header cell.
|
* Expense category header cell.
|
||||||
@@ -128,3 +131,27 @@ export function useExpenseFormTableColumns({ landedCost }) {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense exchange rate input field.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export function ExpensesExchangeRateInputField({ ...props }) {
|
||||||
|
const currentOrganization = useCurrentOrganization();
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
const isForeignJouranl = useExpensesIsForeign();
|
||||||
|
|
||||||
|
// Can't continue if the customer is not foreign.
|
||||||
|
if (!isForeignJouranl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ExchangeRateInputGroup
|
||||||
|
fromCurrency={values.currency_code}
|
||||||
|
toCurrency={currentOrganization.base_currency}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useFormikContext } from 'formik';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { first } from 'lodash';
|
import { first, sumBy } from 'lodash';
|
||||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +14,9 @@ import {
|
|||||||
repeatValue,
|
repeatValue,
|
||||||
ensureEntriesHasEmptyLine,
|
ensureEntriesHasEmptyLine,
|
||||||
orderingLinesIndexes,
|
orderingLinesIndexes,
|
||||||
|
formattedAmount,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
|
import { useCurrentOrganization } from 'hooks/state';
|
||||||
|
|
||||||
const ERROR = {
|
const ERROR = {
|
||||||
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
|
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
|
||||||
@@ -150,3 +152,45 @@ export const useSetPrimaryBranchToForm = () => {
|
|||||||
}
|
}
|
||||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retreives the Journal totals.
|
||||||
|
*/
|
||||||
|
export const useExpensesTotals = () => {
|
||||||
|
const {
|
||||||
|
values: { categories, currency_code: currencyCode },
|
||||||
|
} = useFormikContext();
|
||||||
|
|
||||||
|
const total = sumBy(categories, 'amount');
|
||||||
|
|
||||||
|
// Retrieves the formatted total money.
|
||||||
|
const formattedTotal = React.useMemo(
|
||||||
|
() => formattedAmount(total, currencyCode),
|
||||||
|
[total, currencyCode],
|
||||||
|
);
|
||||||
|
// Retrieves the formatted subtotal.
|
||||||
|
const formattedSubtotal = React.useMemo(
|
||||||
|
() => formattedAmount(total, currencyCode, { money: false }),
|
||||||
|
[total, currencyCode],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedTotal,
|
||||||
|
formattedSubtotal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines whether the expenses has foreign .
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const useExpensesIsForeign = () => {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
const currentOrganization = useCurrentOrganization();
|
||||||
|
|
||||||
|
const isForeignExpenses = React.useMemo(
|
||||||
|
() => values.currency_code !== currentOrganization.base_currency,
|
||||||
|
[values.currency_code, currentOrganization.base_currency],
|
||||||
|
);
|
||||||
|
return isForeignExpenses;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,42 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormGroup, TextArea } from '@blueprintjs/core';
|
import styled from 'styled-components';
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import { FastField, ErrorMessage } from 'formik';
|
|
||||||
import { Row, Col, Postbox } from 'components';
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { inputIntent } from 'utils';
|
import { Paper, Row, Col } from 'components';
|
||||||
|
import { WarehouseTransferFormFooterLeft } from './WarehouseTransferFormFooterLeft';
|
||||||
|
|
||||||
export default function WarehouseTransferFormFooter() {
|
export default function WarehouseTransferFormFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<Postbox
|
<WarehousesTransferFooterPaper>
|
||||||
title={<T id={'warehouse_transfer.label.warehouse_transfer_detail'} />}
|
|
||||||
defaultOpen={false}
|
|
||||||
>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
{/*------------ reason -----------*/}
|
<WarehouseTransferFormFooterLeft />
|
||||||
<FastField name={'reason'}>
|
|
||||||
{({ field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'reason'} />}
|
|
||||||
className={'form-group--reason'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'reason'} />}
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
growVertically={true}
|
|
||||||
large={true}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Postbox>
|
</WarehousesTransferFooterPaper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WarehousesTransferFooterPaper = styled(Paper)`
|
||||||
|
padding: 20px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
export function WarehouseTransferFormFooterLeft() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* --------- Terms and conditions --------- */}
|
||||||
|
<TermsConditsFormGroup label={<T id={'reason'} />} name={'reason'}>
|
||||||
|
<FEditableText
|
||||||
|
name={'reason'}
|
||||||
|
placeholder={
|
||||||
|
'Enter the terms and conditions of your business to be displayed in your transaction'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TermsConditsFormGroup>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TermsConditsFormGroup = styled(FFormGroup)`
|
||||||
|
&.bp3-form-group {
|
||||||
|
.bp3-label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.bp3-form-content {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user