feat: add warehouse transfer & expenses & journal.

This commit is contained in:
elforjani13
2022-03-20 20:13:49 +02:00
parent 23261e975d
commit 64d73fa7b9
15 changed files with 388 additions and 149 deletions

View File

@@ -9,7 +9,6 @@ import { FastField, ErrorMessage } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'components';
import classNames from 'classnames';
import { Features } from 'common';
import { CLASSES } from 'common/classes';
import {
@@ -24,14 +23,12 @@ import {
FieldHint,
FieldRequiredHint,
Icon,
If,
FeatureCan,
InputPrependButton,
CurrencySelectList,
ExchangeRateInputGroup,
} from 'components';
import withSettings from 'containers/Settings/withSettings';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { JournalExchangeRateInputField } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import {
currenciesFieldShouldUpdate,
@@ -52,13 +49,7 @@ function MakeJournalEntriesHeader({
journalNextNumber,
journalNumberPrefix,
}) {
const {
currencies,
isForeignJournal,
baseCurrency,
selectJournalCurrency,
setSelactJournalCurrency,
} = useMakeJournalFormContext();
const { currencies } = useMakeJournalFormContext();
// Handle journal number change.
const handleJournalNumberChange = () => {
@@ -195,45 +186,35 @@ function MakeJournalEntriesHeader({
</FastField>
{/*------------ Currency -----------*/}
<FeatureCan feature={Features.ManualJournal}>
<FastField
name={'currency_code'}
currencies={currencies}
shouldUpdate={currenciesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}
className={classNames('form-group--currency', CLASSES.FILL)}
inline={true}
>
<CurrencySelectList
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue(
'currency_code',
currencyItem.currency_code,
);
form.setFieldValue('exchange_rate', '');
setSelactJournalCurrency(currencyItem);
}}
defaultSelectText={value}
/>
</FormGroup>
)}
</FastField>
</FeatureCan>
<FastField
name={'currency_code'}
currencies={currencies}
shouldUpdate={currenciesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}
className={classNames('form-group--currency', CLASSES.FILL)}
inline={true}
>
<CurrencySelectList
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue('currency_code', currencyItem.currency_code);
form.setFieldValue('exchange_rate', '');
}}
defaultSelectText={value}
/>
</FormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */}
<If condition={isForeignJournal}>
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectJournalCurrency?.currency_code}
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
</If>
<JournalExchangeRateInputField
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
</div>
);
}

View File

@@ -1,44 +1,29 @@
import React from 'react';
import { FastField } from 'formik';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from 'common/classes';
import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { Postbox, ErrorMessage, Row, Col } from 'components';
import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils';
import { Row, Col, Paper } from 'components';
import { MakeJournalFormFooterLeft } from './MakeJournalFormFooterLeft';
import { MakeJournalFormFooterRight } from './MakeJournalFormFooterRight';
export default function MakeJournalFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Postbox title={<T id={'journal_details'} />} defaultOpen={false}>
<MakeJournalFooterPaper>
<Row>
<Col md={8}>
<FastField name={'description'}>
{({ 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>
<MakeJournalFormFooterLeft />
</Col>
<Col md={4}>
<Dragzone
initialFiles={[]}
// onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile}
hint={<T id={'attachments_maximum'} />}
/>
<MakeJournalFormFooterRight />
</Col>
</Row>
</Postbox>
</MakeJournalFooterPaper>
</div>
);
}
const MakeJournalFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -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;
}
}
`;

View File

@@ -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;
`;

View File

@@ -10,6 +10,10 @@ import {
BranchesListFieldCell,
} from 'components/DataTableCells';
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';
/**
@@ -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}
/>
);
}

View File

@@ -10,11 +10,14 @@ import {
transformToForm,
defaultFastFieldShouldUpdate,
ensureEntriesHasEmptyLine,
formattedAmount,
safeSumBy,
} from 'utils';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
import { useFormikContext } from 'formik';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { useCurrentOrganization } from 'hooks/state';
const ERROR = {
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
@@ -214,3 +217,48 @@ export const useSetPrimaryBranchToForm = () => {
}
}, [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;
};

View File

@@ -1,41 +1,30 @@
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 { inputIntent } from 'utils';
import { Row, Dragzone, Col, Postbox } from 'components';
import styled from 'styled-components';
import { CLASSES } from 'common/classes';
import { Row, Col, Paper } from 'components';
import { ExpenseFormFooterLeft } from './ExpenseFormFooterLeft';
import { ExpenseFormFooterRight } from './ExpenseFormFooterRight';
export default function ExpenseFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Postbox title={<T id={'expense_details'} />} defaultOpen={false}>
<ExpensesFooterPaper>
<Row>
<Col md={8}>
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
>
<TextArea growVertically={true} {...field} />
</FormGroup>
)}
</FastField>
<ExpenseFormFooterLeft />
</Col>
<Col md={4}>
<Dragzone
initialFiles={[]}
// onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile}
hint={<T id={'attachments_maximum'} />}
/>
<ExpenseFormFooterRight />
</Col>
</Row>
</Postbox>
</ExpensesFooterPaper>
</div>
);
}
const ExpensesFooterPaper = styled(Paper)`
padding: 20px;
`;

View 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;
}
}
`;

View File

@@ -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;
`;

View File

@@ -23,10 +23,9 @@ import {
CustomerSelectField,
AccountsSelectList,
FieldRequiredHint,
ExchangeRateInputGroup,
Hint,
If,
} from 'components';
import { ExpensesExchangeRateInputField } from './components';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
@@ -34,15 +33,7 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
* Expense form header.
*/
export default function ExpenseFormHeader() {
const {
currencies,
accounts,
customers,
isForeignCustomer,
baseCurrency,
selectCustomer,
setSelectCustomer,
} = useExpenseFormContext();
const { currencies, accounts, customers } = useExpenseFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
@@ -118,7 +109,6 @@ export default function ExpenseFormHeader() {
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue('currency_code', currencyItem.currency_code);
setSelectCustomer(currencyItem);
}}
defaultSelectText={value}
/>
@@ -126,14 +116,11 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<If condition={isForeignCustomer}>
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectCustomer?.currency_code}
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
</If>
{/* ----------- Exchange rate ----------- */}
<ExpensesExchangeRateInputField
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (

View File

@@ -9,7 +9,10 @@ import {
AccountsListFieldCell,
CheckBoxFieldCell,
} 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.
@@ -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}
/>
);
}

View File

@@ -5,7 +5,7 @@ import { useFormikContext } from 'formik';
import moment from 'moment';
import intl from 'react-intl-universal';
import * as R from 'ramda';
import { first } from 'lodash';
import { first, sumBy } from 'lodash';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import {
@@ -14,7 +14,9 @@ import {
repeatValue,
ensureEntriesHasEmptyLine,
orderingLinesIndexes,
formattedAmount,
} from 'utils';
import { useCurrentOrganization } from 'hooks/state';
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
@@ -150,3 +152,45 @@ export const useSetPrimaryBranchToForm = () => {
}
}, [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;
};

View File

@@ -1,42 +1,25 @@
import React from 'react';
import classNames from 'classnames';
import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { FastField, ErrorMessage } from 'formik';
import { Row, Col, Postbox } from 'components';
import styled from 'styled-components';
import { CLASSES } from 'common/classes';
import { inputIntent } from 'utils';
import { Paper, Row, Col } from 'components';
import { WarehouseTransferFormFooterLeft } from './WarehouseTransferFormFooterLeft';
export default function WarehouseTransferFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Postbox
title={<T id={'warehouse_transfer.label.warehouse_transfer_detail'} />}
defaultOpen={false}
>
<WarehousesTransferFooterPaper>
<Row>
<Col md={8}>
{/*------------ reason -----------*/}
<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>
<WarehouseTransferFormFooterLeft />
</Col>
</Row>
</Postbox>
</WarehousesTransferFooterPaper>
</div>
);
}
const WarehousesTransferFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -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;
}
}
`;