refactoring: account form.

refactoring: expense form.
refactoring: manual journal form.
refactoring: invoice form.
This commit is contained in:
a.bouhuolia
2021-02-15 12:03:47 +02:00
parent 692f3b333a
commit 760c38b54b
124 changed files with 2694 additions and 2967 deletions

View File

@@ -11,63 +11,67 @@ import {
} from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import classNames from 'classnames';
import { saveInvoke } from 'utils';
import { Icon, If } from 'components';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
/**
* Expense form floating actions.
*/
export default function ExpenseFloatingFooter({
isSubmitting,
onSubmitClick,
onCancelClick,
expense,
expensePublished,
}) {
const { submitForm, resetForm } = useFormikContext();
export default function ExpenseFloatingFooter() {
const history = useHistory();
// Formik context.
const { isSubmitting, submitForm, resetForm } = useFormikContext();
// Expense form context.
const { setSubmitPayload, isNewMode } = useExpenseFormContext();
// Handle submit & publish button click.
const handleSubmitPublishBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { redirect: true, publish: true});
setSubmitPayload({ redirect: true, publish: true});
submitForm();
};
// Handle submit, publish & new button click.
const handleSubmitPublishAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: true,
resetForm: true,
});
};
// Handle submit, publish & continue editing button click.
const handleSubmitPublishContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, publish: true });
submitForm();
saveInvoke(onSubmitClick, event, { redirect: false, publish: true });
};
// Handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { redirect: true, publish: false });
setSubmitPayload({ redirect: true, publish: false });
submitForm();
};
// Handle submit as draft & new button click.
const handleSubmitDraftAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: false,
resetForm: true,
});
};
// Handles submit as draft & continue editing button click.
const handleSubmitDraftContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, publish: false });
submitForm();
saveInvoke(onSubmitClick, event, { redirect: false, publish: false });
};
// Handle cancel button click.
const handleCancelBtnClick = (event) => {
saveInvoke(onCancelClick, event);
history.goBack();
};
// Handles clear form button click.
const handleClearBtnClick = (event) => {
resetForm();
};
@@ -75,10 +79,11 @@ export default function ExpenseFloatingFooter({
return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save And Publish ----------- */}
<If condition={!expense || !expensePublished}>
<If condition={isNewMode}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitPublishBtnClick}
text={<T id={'save_publish'} />}
@@ -140,10 +145,11 @@ export default function ExpenseFloatingFooter({
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={expense && expensePublished}>
<If condition={!isNewMode}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitPublishBtnClick}
text={<T id={'save'} />}
@@ -174,7 +180,7 @@ export default function ExpenseFloatingFooter({
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={expense ? <T id={'reset'} /> : <T id={'clear'} />}
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button

View File

@@ -1,9 +1,8 @@
import React, { useMemo, useEffect, useState, useCallback } from 'react';
import React, { useMemo } from 'react';
import { Intent } from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import { defaultTo, pick, sumBy } from 'lodash';
import { defaultTo, sumBy, isEmpty } from 'lodash';
import { Formik, Form } from 'formik';
import moment from 'moment';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
@@ -24,80 +23,51 @@ import {
CreateExpenseFormSchema,
EditExpenseFormSchema,
} from './ExpenseForm.schema';
import { transformErrors } from './utils';
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
const MIN_LINES_NUMBER = 4;
const defaultCategory = {
index: 0,
amount: '',
expense_account_id: '',
description: '',
};
const defaultInitialValues = {
payment_account_id: '',
beneficiary: '',
payment_date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference_no: '',
currency_code: '',
publish: '',
categories: [...repeatValue(defaultCategory, MIN_LINES_NUMBER)],
};
import { transformErrors, defaultExpense, transformToEditForm } from './utils';
import { compose, orderingLinesIndexes } from 'utils';
/**
* Expense form.
*/
function ExpenseForm({
// #withDashboard
changePageTitle,
// #withSettings
baseCurrency,
preferredPaymentAccount,
}) {
// Expense form context.
const {
editExpenseMutate,
createExpenseMutate,
expense,
expenseId,
submitPayload,
} = useExpenseFormContext();
const isNewMode = !expenseId;
const [submitPayload, setSubmitPayload] = useState({});
const { formatMessage } = useIntl();
// History context.
const history = useHistory();
useEffect(() => {
if (isNewMode) {
changePageTitle(formatMessage({ id: 'new_expense' }));
} else {
changePageTitle(formatMessage({ id: 'edit_expense' }));
}
}, [changePageTitle, isNewMode, formatMessage]);
// Form initial values.
const initialValues = useMemo(
() => ({
...(expense
...(!isEmpty(expense)
? {
...pick(expense, Object.keys(defaultInitialValues)),
categories: [
...expense.categories.map((category) => ({
...pick(category, Object.keys(defaultCategory)),
})),
],
...transformToEditForm(expense, defaultExpense),
}
: {
...defaultInitialValues,
...defaultExpense,
currency_code: baseCurrency,
payment_account_id: defaultTo(preferredPaymentAccount, ''),
categories: orderingLinesIndexes(defaultInitialValues.categories),
categories: orderingLinesIndexes(defaultExpense.categories),
}),
}),
[expense, baseCurrency, preferredPaymentAccount],
[
expense,
baseCurrency,
preferredPaymentAccount,
],
);
// Handle form submit.
@@ -155,21 +125,11 @@ function ExpenseForm({
if (isNewMode) {
createExpenseMutate(form).then(handleSuccess).catch(handleError);
} else {
editExpenseMutate(expense.id, form)
editExpenseMutate([expense.id, form])
.then(handleSuccess)
.catch(handleError);
}
};
const handleCancelClick = useCallback(() => {
history.goBack();
}, [history]);
const handleSubmitClick = useCallback(
(event, payload) => {
setSubmitPayload({ ...payload });
},
[setSubmitPayload],
);
return (
<div
@@ -180,32 +140,24 @@ function ExpenseForm({
)}
>
<Formik
validationSchema={isNewMode
? CreateExpenseFormSchema
: EditExpenseFormSchema}
validationSchema={
isNewMode ? CreateExpenseFormSchema : EditExpenseFormSchema
}
initialValues={initialValues}
onSubmit={handleSubmit}
>
{({ isSubmitting, values }) => (
<Form>
<ExpenseFormHeader />
<ExpenseFormBody />
<ExpenseFormFooter />
<ExpenseFloatingFooter
isSubmitting={isSubmitting}
expense={expenseId}
expensePublished={values.publish}
onCancelClick={handleCancelClick}
onSubmitClick={handleSubmitClick}
/>
</Form>
)}
<Form>
<ExpenseFormHeader />
<ExpenseFormBody />
<ExpenseFormFooter />
<ExpenseFloatingFooter />
</Form>
</Formik>
</div>
);
}
export default compose(
export default compose(
withDashboardActions,
withMediaActions,
withSettings(({ organizationSettings, expenseSettings }) => ({

View File

@@ -3,7 +3,9 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ExpenseFormEntriesField from './ExpenseFormEntriesField';
export default function ExpenseFormBody() {
export default function ExpenseFormBody({
}) {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<ExpenseFormEntriesField />

View File

@@ -1,143 +0,0 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { omit } from 'lodash';
import { DataTableEditable, Icon } from 'components';
import { Hint } from 'components';
import {
formattedAmount,
transformUpdatedRows,
saveInvoke,
} from 'utils';
import {
AccountsListFieldCell,
MoneyFieldCell,
InputGroupCell,
} from 'components/DataTableCells';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { useExpenseFormTableColumns } from './components';
export default function ExpenseTable({
// #ownPorps
onClickRemoveRow,
onClickAddNewRow,
onClickClearAllLines,
entries,
error,
onChange,
}) {
const [rows, setRows] = useState([]);
const { formatMessage } = useIntl();
const { accounts } = useExpenseFormContext();
useEffect(() => {
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
}, [entries]);
// Final table rows editor rows and total and final blank row.
const tableRows = useMemo(() => [...rows, { rowType: 'total' }], [rows]);
// Memorized data table columns.
const columns = useExpenseFormTableColumns();
// Handles update datatable data.
const handleUpdateData = useCallback(
(rowIndex, columnIdOrObj, value) => {
const newRows = transformUpdatedRows(
rows,
rowIndex,
columnIdOrObj,
value,
);
saveInvoke(
onChange,
newRows
.filter((row) => row.rowType === 'editor')
.map((row) => ({
...omit(row, ['rowType']),
})),
);
},
[rows, onChange],
);
// Handles click remove datatable row.
const handleRemoveRow = useCallback(
(rowIndex) => {
// Can't continue if there is just one row line or less.
if (rows.length <= 1) {
return;
}
const removeIndex = parseInt(rowIndex, 10);
const newRows = rows.filter((row, index) => index !== removeIndex);
saveInvoke(
onChange,
newRows
.filter((row) => row.rowType === 'editor')
.map((row, index) => ({
...omit(row, ['rowType']),
index: index + 1,
})),
);
saveInvoke(onClickRemoveRow, removeIndex);
},
[rows, onChange, onClickRemoveRow],
);
// Invoke when click on add new line button.
const onClickNewRow = () => {
saveInvoke(onClickAddNewRow);
};
// Invoke when click on clear all lines button.
const handleClickClearAllLines = () => {
saveInvoke(onClickClearAllLines);
};
// Rows classnames callback.
const rowClassNames = useCallback(
(row) => ({
'row--total': rows.length === row.index + 1,
}),
[rows],
);
return (
<DataTableEditable
columns={columns}
data={tableRows}
rowClassNames={rowClassNames}
sticky={true}
payload={{
accounts: accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
autoFocus: ['expense_account_id', 0],
}}
actions={
<>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button
small={true}
className={'button--secondary button--clear-lines ml1'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
</>
}
totalRow={true}
/>
);
}

View File

@@ -1,30 +1,27 @@
import { FastField } from 'formik';
import React from 'react';
import ExpenseFormEntries from './ExpenseFormEntries';
import { orderingLinesIndexes, repeatValue } from 'utils';
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
/**
* Expense form entries field.
*/
export default function ExpenseFormEntriesField({
defaultRow,
linesNumber = 4,
}) {
const { defaultCategoryEntry } = useExpenseFormContext();
return (
<FastField name={'categories'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<ExpenseFormEntries
<ExpenseFormEntriesTable
entries={value}
error={error}
onChange={(entries) => {
form.setFieldValue('categories', entries);
}}
onClickAddNewRow={() => {
form.setFieldValue('categories', [...value, defaultRow]);
}}
onClickClearAllLines={() => {
form.setFieldValue(
'categories',
orderingLinesIndexes([...repeatValue(defaultRow, linesNumber)])
);
}}
defaultEntry={defaultCategoryEntry}
linesNumber={linesNumber}
/>
)}
</FastField>

View File

@@ -0,0 +1,121 @@
import React, { useCallback } from 'react';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { DataTableEditable } from 'components';
import ExpenseDeleteEntriesAlert from 'containers/Alerts/Expenses/ExpenseDeleteEntriesAlert';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { useExpenseFormTableColumns } from './components';
import withAlertActions from 'containers/Alert/withAlertActions';
import { transformUpdatedRows, compose, saveInvoke, repeatValue } from 'utils';
/**
* Expenses form entries.
*/
function ExpenseFormEntriesTable({
// #withAlertActions
openAlert,
// #ownPorps
entries,
defaultEntry,
error,
onChange,
}) {
// Expense form context.
const { accounts } = useExpenseFormContext();
// Memorized data table columns.
const columns = useExpenseFormTableColumns();
// Handles update datatable data.
const handleUpdateData = useCallback(
(rowIndex, columnIdOrObj, value) => {
const newRows = transformUpdatedRows(
entries,
rowIndex,
columnIdOrObj,
value,
);
saveInvoke(onChange, newRows);
},
[entries, onChange],
);
// Handles click remove datatable row.
const handleRemoveRow = useCallback(
(rowIndex) => {
// Can't continue if there is just one row line or less.
if (entries.length <= 1) {
return;
}
const newRows = entries.filter((row, index) => index !== rowIndex);
saveInvoke(onChange, newRows);
},
[entries, onChange],
);
// Invoke when click on add new line button.
const onClickNewRow = () => {
const newRows = [...entries, defaultEntry];
saveInvoke(onChange, newRows);
};
// Invoke when click on clear all lines button.
const handleClickClearAllLines = () => {
openAlert('expense-delete-entries');
};
// handle confirm clear all entries alert.
const handleConfirmClearEntriesAlert = () => {
const newRows = repeatValue(defaultEntry, 3);
saveInvoke(onChange, newRows);
};
return (
<>
<DataTableEditable
columns={columns}
data={entries}
sticky={true}
payload={{
accounts: accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
autoFocus: ['expense_account_id', 0],
}}
actions={
<>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button
small={true}
className={'button--secondary button--clear-lines ml1'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
</>
}
totalRow={true}
/>
<ExpenseDeleteEntriesAlert
name={'expense-delete-entries'}
onConfirm={handleConfirmClearEntriesAlert}
/>
</>
);
}
export default compose(
withAlertActions
)(ExpenseFormEntriesTable);

View File

@@ -7,7 +7,7 @@ import { inputIntent } from 'utils';
import { Row, Dragzone, Col } from 'components';
import { CLASSES } from 'common/classes';
export default function ExpenseFormFooter({}) {
export default function ExpenseFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row>

View File

@@ -6,7 +6,6 @@ import { FormattedMessage as T } from 'react-intl';
import { CLASSES } from 'common/classes';
import {
momentFormatter,
compose,
tansformDateValue,
inputIntent,
handleDateChange,
@@ -27,7 +26,7 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
/**
* Expense form header.
*/
export default function ExpenseFormHeader({}) {
export default function ExpenseFormHeader() {
const { currencies, accounts, customers } = useExpenseFormContext();
return (

View File

@@ -1,48 +1,21 @@
import React, { useCallback, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import React from 'react';
import { useParams } from 'react-router-dom';
import ExpenseForm from './ExpenseForm';
import { ExpenseFormPageProvider } from './ExpenseFormPageProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
import 'style/pages/Expense/PageForm.scss';
/**
* Expense page form.
*/
function ExpenseFormPage({
// #withDashboardActions
setSidebarShrink,
resetSidebarPreviousExpand,
setDashboardBackLink,
}) {
const history = useHistory();
export default function ExpenseFormPage() {
const { id } = useParams();
useEffect(() => {
// Shrink the sidebar by foce.
setSidebarShrink();
// Show the back link on dashboard topbar.
setDashboardBackLink(true);
return () => {
// Reset the sidebar to the previous status.
resetSidebarPreviousExpand();
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
}, [resetSidebarPreviousExpand, setSidebarShrink, setDashboardBackLink]);
return (
<ExpenseFormPageProvider expenseId={id}>
<ExpenseForm />
</ExpenseFormPageProvider>
);
}
export default compose(
withDashboardActions,
)(ExpenseFormPage);

View File

@@ -24,7 +24,12 @@ function ExpenseFormPageProvider({ expenseId, ...props }) {
} = useCustomers();
// Fetch the expense details.
const { data: expense, isFetching: isExpenseLoading } = useExpense(expenseId);
const { data: expense, isFetching: isExpenseLoading } = useExpense(
expenseId,
{
enabled: !!expenseId,
},
);
// Fetch accounts list.
const { data: accounts, isFetching: isAccountsLoading } = useAccounts();
@@ -33,9 +38,17 @@ function ExpenseFormPageProvider({ expenseId, ...props }) {
const { mutateAsync: createExpenseMutate } = useCreateExpense();
const { mutateAsync: editExpenseMutate } = useEditExpense();
// Submit form payload.
const [submitPayload, setSubmitPayload] = React.useState({});
//
const isNewMode = !expenseId;
// Provider payload.
const provider = {
isNewMode,
expenseId,
submitPayload,
currencies,
customers,
@@ -49,6 +62,7 @@ function ExpenseFormPageProvider({ expenseId, ...props }) {
createExpenseMutate,
editExpenseMutate,
setSubmitPayload,
};
return (

View File

@@ -1,6 +1,17 @@
import React from 'react';
import { Button, Tooltip, Intent, Position } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Icon, Hint } from 'components';
import {
InputGroupCell,
MoneyFieldCell,
AccountsListFieldCell,
} from 'components/DataTableCells';
import { formattedAmount, safeSumBy } from 'utils';
/**
* Expense category header cell.
*/
const ExpenseCategoryHeaderCell = () => {
return (
<>
@@ -10,7 +21,9 @@ const ExpenseCategoryHeaderCell = () => {
);
};
// Actions cell renderer.
/**
* Actions cell renderer.
*/
const ActionsCellRenderer = ({
row: { index },
column: { id },
@@ -18,9 +31,6 @@ const ActionsCellRenderer = ({
data,
payload,
}) => {
if (data.length <= index + 1) {
return '';
}
const onClickRemoveRole = () => {
payload.removeRow(index);
};
@@ -38,95 +48,76 @@ const ActionsCellRenderer = ({
);
};
// Total text cell renderer.
const TotalExpenseCellRenderer = (chainedComponent) => (props) => {
if (props.data.length <= props.row.index + 1) {
return (
<span>
<T id={'total_currency'} values={{ currency: 'USD' }} />
</span>
);
}
return chainedComponent(props);
};
/**
* Amount footer cell.
*/
function AmountFooterCell({ rows }) {
const total = safeSumBy(rows, 'original.amount');
return <span>{formattedAmount(total, 'USD')}</span>;
}
/**
* Note cell renderer.
* Expense account footer cell.
*/
const NoteCellRenderer = (chainedComponent) => (props) => {
if (props.data.length === props.row.index + 1) {
return '';
}
return chainedComponent(props);
};
function ExpenseAccountFooterCell() {
return 'Total';
}
/**
* Total amount cell renderer.
* Retrieve expense form table entries columns.
*/
const TotalAmountCellRenderer = (chainedComponent, type) => (props) => {
if (props.data.length === props.row.index + 1) {
const total = props.data.reduce((total, entry) => {
const amount = parseInt(entry[type], 10);
const computed = amount ? total + amount : total;
return computed;
}, 0);
return <span>{formattedAmount(total, 'USD')}</span>;
}
return chainedComponent(props);
};
export function useExpenseFormTableColumns() {
const { formatMessage } = useIntl();
export function useExpenseFormTableColumns() {
return React.useMemo(
() => [
{
Header: '#',
accessor: 'index',
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
className: 'index',
width: 40,
disableResizing: true,
disableSortBy: true,
},
{
Header: ExpenseCategoryHeaderCell,
id: 'expense_account_id',
accessor: 'expense_account_id',
Cell: TotalExpenseCellRenderer(AccountsListFieldCell),
className: 'expense_account_id',
disableSortBy: true,
width: 40,
filterAccountsByRootType: ['expense'],
},
{
Header: formatMessage({ id: 'amount_currency' }, { currency: 'USD' }),
accessor: 'amount',
Cell: TotalAmountCellRenderer(MoneyFieldCell, 'amount'),
disableSortBy: true,
width: 40,
className: 'amount',
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'description',
Cell: NoteCellRenderer(InputGroupCell),
disableSortBy: true,
className: 'description',
width: 100,
},
{
Header: '',
accessor: 'action',
Cell: ActionsCellRenderer,
className: 'actions',
disableSortBy: true,
disableResizing: true,
width: 45,
},
],
[formatMessage],
)
}
return React.useMemo(
() => [
{
Header: '#',
accessor: 'index',
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
className: 'index',
width: 40,
disableResizing: true,
disableSortBy: true,
},
{
Header: ExpenseCategoryHeaderCell,
id: 'expense_account_id',
accessor: 'expense_account_id',
Cell: AccountsListFieldCell,
Footer: ExpenseAccountFooterCell,
className: 'expense_account_id',
disableSortBy: true,
width: 40,
filterAccountsByRootType: ['expense'],
},
{
Header: formatMessage({ id: 'amount_currency' }, { currency: 'USD' }),
accessor: 'amount',
Cell: MoneyFieldCell,
Footer: AmountFooterCell,
disableSortBy: true,
width: 40,
className: 'amount',
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'description',
Cell: InputGroupCell,
disableSortBy: true,
className: 'description',
width: 100,
},
{
Header: '',
accessor: 'action',
Cell: ActionsCellRenderer,
className: 'actions',
disableSortBy: true,
disableResizing: true,
width: 45,
},
],
[formatMessage],
);
}

View File

@@ -1,5 +1,7 @@
import { AppToaster } from 'components';
import moment from 'moment';
import { formatMessage } from 'services/intl';
import { transformToForm, repeatValue } from 'utils';
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
@@ -18,4 +20,46 @@ export const transformErrors = (errors, { setErrors }) => {
}),
);
}
};
};
export const MIN_LINES_NUMBER = 4;
export const defaultExpenseEntry = {
index: 0,
amount: '',
expense_account_id: '',
description: '',
};
export const defaultExpense = {
payment_account_id: '',
beneficiary: '',
payment_date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference_no: '',
currency_code: '',
publish: '',
categories: [...repeatValue(defaultExpenseEntry, MIN_LINES_NUMBER)],
};
/**
* Transformes the expense to form initial values in edit mode.
*/
export const transformToEditForm = (
expense,
defaultExpense,
linesNumber = 4,
) => {
return {
...transformToForm(expense, defaultExpense),
categories: [
...expense.categories.map((category) => ({
...transformToForm(category, defaultExpense.categories[0]),
})),
...repeatValue(
expense,
Math.max(linesNumber - expense.categories.length, 0),
),
],
};
};