WIP: Feature JournalNumber dialog / Fix: ItemCategory & EditMakeJournal & Expenses

This commit is contained in:
elforjani3
2020-10-19 19:53:10 +02:00
parent 00ba1bb75e
commit d804007596
12 changed files with 413 additions and 94 deletions

View File

@@ -5,6 +5,7 @@ import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog';
import JournalNumberDailog from 'containers/Dialogs/JournalNumberDailog';
export default function DialogsContainer() {
return (
@@ -15,6 +16,7 @@ export default function DialogsContainer() {
<ItemCategoryDialog />
<AccountFormDialog />
{/* <UserFormDialog /> */}
<JournalNumberDailog />
</div>
);
}

View File

@@ -6,10 +6,11 @@ export default function MakeJournalEntriesFooter({
formik: { isSubmitting },
onSubmitClick,
onCancelClick,
manualJournal,
}) {
return (
<div>
<div class='form__floating-footer'>
<div class="form__floating-footer">
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
@@ -18,7 +19,11 @@ export default function MakeJournalEntriesFooter({
onSubmitClick({ publish: true, redirect: true });
}}
>
<T id={'save'} />
{manualJournal && manualJournal.id ? (
<T id={'edit'} />
) : (
<T id={'save'} />
)}
</Button>
<Button
@@ -49,7 +54,7 @@ export default function MakeJournalEntriesFooter({
onCancelClick && onCancelClick();
}}
>
<T id={'cancel'}/>
<T id={'cancel'} />
</Button>
</div>
</div>

View File

@@ -34,7 +34,8 @@ const ERROR = {
VENDORS_NOT_WITH_PAYABLE_ACCOUNT: 'VENDORS.NOT.WITH.PAYABLE.ACCOUNT',
PAYABLE_ENTRIES_HAS_NO_VENDORS: 'PAYABLE.ENTRIES.HAS.NO.VENDORS',
RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS',
CREDIT_DEBIT_SUMATION_SHOULD_NOT_EQUAL_ZERO:'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO'
CREDIT_DEBIT_SUMATION_SHOULD_NOT_EQUAL_ZERO:
'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO',
};
/**
@@ -58,7 +59,6 @@ function MakeJournalEntriesForm({
onFormSubmit,
onCancelForm,
}) {
const { formatMessage } = useIntl();
const {
setFiles,
@@ -75,7 +75,6 @@ function MakeJournalEntriesForm({
setFiles(_files.filter((file) => file.uploaded === false));
}, []);
const savedMediaIds = useRef([]);
const clearSavedMediaIds = () => {
savedMediaIds.current = [];
@@ -140,6 +139,7 @@ function MakeJournalEntriesForm({
const defaultEntry = useMemo(
() => ({
index: 0,
account_id: null,
credit: 0,
debit: 0,
@@ -251,7 +251,7 @@ function MakeJournalEntriesForm({
}),
intent: Intent.DANGER,
});
}
}
};
const formik = useFormik({
@@ -262,7 +262,7 @@ function MakeJournalEntriesForm({
},
onSubmit: async (values, { setErrors, setSubmitting, resetForm }) => {
const entries = values.entries.filter(
(entry) => entry.credit || entry.debit,
(entry) => entry.debit || entry.credit,
);
const getTotal = (type = 'credit') => {
return entries.reduce((total, item) => {
@@ -413,6 +413,7 @@ function MakeJournalEntriesForm({
formik={formik}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick}
manualJournal={manualJournal}
/>
</form>

View File

@@ -5,6 +5,7 @@ import {
Intent,
Position,
Classes,
Button,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl';
@@ -18,11 +19,18 @@ import {
Hint,
FieldHint,
FieldRequiredHint,
Icon,
} from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions';
export default function MakeJournalEntriesHeader({
import { compose } from 'utils';
function MakeJournalEntriesHeader({
formik: { errors, touched, values, setFieldValue, getFieldProps },
// #withDialog
openDialog,
}) {
const handleDateChange = useCallback(
(date) => {
@@ -31,6 +39,9 @@ export default function MakeJournalEntriesHeader({
},
[setFieldValue],
);
const handleJournalNumberChange = useCallback(() => {
openDialog('journalNumber-form', {});
}, [openDialog]);
return (
<div class="make-journal-entries__header">
@@ -62,7 +73,15 @@ export default function MakeJournalEntriesHeader({
/>
</FormGroup>
</Col>
<Col width={50}>
{/* <FormGroup> */}
<Button
className={classNames('btn', Classes.SMALL)}
icon={<Icon icon="setting" />}
onClick={handleJournalNumberChange}
/>
{/* </FormGroup> */}
</Col>
<Col width={220}>
<FormGroup
label={<T id={'date'} />}
@@ -153,3 +172,5 @@ export default function MakeJournalEntriesHeader({
</div>
);
}
export default compose(withDialogActions)(MakeJournalEntriesHeader);

View File

@@ -78,15 +78,9 @@ function ItemCategoryDialog({
.required()
.label(formatMessage({ id: 'category_name_' })),
parent_category_id: Yup.string().nullable(),
cost_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'cost_account_' })),
sell_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'sell_account_' })),
inventory_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'inventory_account_' })),
cost_account_id: Yup.number().nullable(),
sell_account_id: Yup.number().nullable(),
inventory_account_id: Yup.number(),
description: Yup.string().trim().nullable(),
});
@@ -122,7 +116,7 @@ function ItemCategoryDialog({
onSubmit: (values, { setSubmitting }) => {
const afterSubmit = () => {
closeDialog(dialogName);
queryCache.invalidateQueries('items-categories-table');
queryCache.invalidateQueries('items-categories-list');
queryCache.invalidateQueries('accounts-list');
};
if (payload.action === 'edit') {

View File

@@ -0,0 +1,71 @@
import React, { useEffect, useCallback, useMemo } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useQuery, queryCache } from 'react-query';
import { Dialog } from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDialogRedux from 'components/DialogReduxConnect';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { compose, optionsMapToArray } from 'utils';
function JournalNumberDailog({
dialogName,
payload,
isOpen,
// #withSettingsActions
requestSubmitOptions,
// #withDialogActions
closeDialog,
}) {
// Handles dialog close.
const handleClose = useCallback(() => {
closeDialog(dialogName);
}, [closeDialog, dialogName]);
const handleSubmitForm = useCallback(() => {});
// Handle dialog on closed.
// const onDialogClosed = useCallback(() => {
// resetForm();
// }, [resetForm]);
return (
<Dialog
name={dialogName}
// isLoading={}
title={<T id={'journal_number_settings'} />}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
onClose={handleClose}
>
<ReferenceNumberForm
onSubmit={handleSubmitForm}
initialNumber={'1000'}
initialPrefix={'A'}
onClose={handleClose}
groupName={'manual_journals'}
/>
</Dialog>
);
}
const mapStateToProps = (state, props) => ({
dialogName: 'journalNumber-form',
journalNumberId: props?.payload?.id || null,
});
const withJournalNumberDailog = connect(mapStateToProps);
export default compose(
withDialogRedux(null, 'journalNumber-form'),
withJournalNumberDailog,
withDialogActions,
withSettingsActions,
)(JournalNumberDailog);

View File

@@ -164,10 +164,10 @@ function ExpenseForm({
...expense.categories.map((category) => ({
...pick(category, Object.keys(defaultCategory)),
})),
...repeatValue(
defaultCategory,
Math.max(MIN_LINES_NUMBER - expense.categories.length, 0),
),
// ...repeatValue(
// defaultCategory,
// Math.max(MIN_LINES_NUMBER - expense.categories.length, 0),
// ),
],
}
: {
@@ -226,10 +226,12 @@ function ExpenseForm({
});
return;
}
const categories = values.categories.filter(
(category) =>
category.amount && category.index && category.expense_account_id,
);
const form = {
...values,
publish: payload.publish,
@@ -329,10 +331,21 @@ function ExpenseForm({
const handleClearAllLines = () => {
formik.setFieldValue(
'categories',
orderingCategoriesIndex([...repeatValue(defaultCategory, MIN_LINES_NUMBER)]),
orderingCategoriesIndex([
...repeatValue(defaultCategory, MIN_LINES_NUMBER),
]),
);
};
const categories = formik.values.categories.filter(
(category) =>
category.amount && category.index && category.expense_account_id,
);
console.log(categories, 'V');
console.log(formik.errors, 'Error');
return (
<div className={'expense-form'}>
<form onSubmit={formik.handleSubmit}>

View File

@@ -22,12 +22,14 @@ import {
} from 'components';
import withCurrencies from 'containers/Currencies/withCurrencies';
import withAccounts from 'containers/Accounts/withAccounts';
import withCustomers from 'containers/Customers/withCustomers';
function ExpenseFormHeader({
formik: { errors, touched, setFieldValue, getFieldProps, values },
currenciesList,
accountsList,
accountsTypes,
customersItems,
}) {
const [selectedItems, setSelectedItems] = useState({});
@@ -84,10 +86,46 @@ function ExpenseFormHeader({
// Filter payment accounts.
const paymentAccounts = useMemo(
() => accountsList.filter(a => a?.type?.key === 'current_asset'),
() => accountsList.filter((a) => a?.type?.key === 'current_asset'),
[accountsList],
);
const CustomerRenderer = useCallback(
(cutomer, { handleClick }) => (
<MenuItem
key={cutomer.id}
text={cutomer.display_name}
onClick={handleClick}
/>
),
[],
);
// Filter Customer
const filterCustomer = (query, customer, _index, exactMatch) => {
const normalizedTitle = customer.display_name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return (
`${customer.display_name} ${normalizedTitle}`.indexOf(
normalizedQuery,
) >= 0
);
}
};
// handle change customer
const onChangeCustomer = useCallback(
(filedName) => {
return (customer) => {
setFieldValue(filedName, customer.id);
};
},
[setFieldValue],
);
return (
<div className={'dashboard__insider--expense-form__header'}>
<Row>
@@ -105,16 +143,16 @@ function ExpenseFormHeader({
}
>
<ListSelect
items={[]}
items={customersItems}
noResults={<MenuItem disabled={true} text="No results." />}
// itemRenderer={}
// itemPredicate={}
itemRenderer={CustomerRenderer}
itemPredicate={filterCustomer}
popoverProps={{ minimal: true }}
// onItemSelect={}
selectedItem={values.beneficiary}
// selectedItemProp={'id'}
defaultText={<T id={'select_customer'} />}
labelProp={'beneficiary'}
onItemSelect={onChangeCustomer('customer_id')}
selectedItem={values.customer_id}
selectedItemProp={'id'}
defaultText={<T id={'select_customer_account'} />}
labelProp={'display_name'}
/>
</FormGroup>
</Col>
@@ -235,4 +273,7 @@ export default compose(
withCurrencies(({ currenciesList }) => ({
currenciesList,
})),
withCustomers(({ customersItems }) => ({
customersItems,
})),
)(ExpenseFormHeader);

View File

@@ -8,6 +8,7 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import { compose } from 'utils';
@@ -21,6 +22,9 @@ function Expenses({
// #wihtCurrenciesActions
requestFetchCurrencies,
// #withCustomersActions
requestFetchCustomers,
}) {
const history = useHistory();
const { id } = useParams();
@@ -38,6 +42,12 @@ function Expenses({
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
// Handle fetch customers data table or list
const fetchCustomers = useQuery('customers-table', () =>
requestFetchCustomers({}),
);
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/expenses-list');
@@ -54,7 +64,8 @@ function Expenses({
loading={
fetchExpense.isFetching ||
fetchAccounts.isFetching ||
fetchCurrencies.isFetching
fetchCurrencies.isFetching ||
fetchCustomers.isFetching
}
name={'expense-form'}
>
@@ -71,4 +82,5 @@ export default compose(
withAccountsActions,
withCurrenciesActions,
withExpensesActions,
withCustomersActions,
)(Expenses);

View File

@@ -0,0 +1,152 @@
import React, { useMemo, useCallback } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { ErrorMessage, AppToaster } from 'components';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
Position,
} from '@blueprintjs/core';
import { compose, optionsMapToArray } from 'utils';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
function ReferenceNumberForm({
onSubmit,
onClose,
initialPrefix,
initialNumber,
groupName,
requestSubmitOptions,
}) {
const { formatMessage } = useIntl();
const validationSchema = Yup.object().shape({
prefix: Yup.string(),
next_number: Yup.number(),
});
const initialValues = useMemo(
() => ({
prefix: initialPrefix || '',
next_number: initialNumber || '',
}),
[],
);
const {
errors,
values,
touched,
setFieldValue,
resetForm,
handleSubmit,
isSubmitting,
getFieldProps,
} = useFormik({
enableReinitialize: true,
initialValues: {
...initialValues,
},
validationSchema,
onSubmit: (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: groupName };
});
onSubmit(
requestSubmitOptions({ options })
.then(() => {
setSubmitting(false);
})
.catch((erros) => {
setSubmitting(false);
}),
);
},
});
// Handles dialog close.
// const handleClose = useCallback(() => {
// closeDialog(dialogName);
// }, [closeDialog, dialogName]);
// Handle dialog on closed.
const onClosed = useCallback(() => {
resetForm();
}, [resetForm]);
return (
<div>
<form onSubmit={handleSubmit}>
<div className={Classes.DIALOG_BODY}>
<p className="paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce
tincidunt porta quam,
</p>
<Row>
{/* prefix */}
<Col>
<FormGroup
label={<T id={'prefix'} />}
className={'form-group--'}
intent={errors.prefix && touched.prefix && Intent.DANGER}
helperText={
<ErrorMessage name={'prefix'} {...{ errors, touched }} />
}
>
<InputGroup
intent={errors.prefix && touched.prefix && Intent.DANGER}
{...getFieldProps('prefix')}
/>
</FormGroup>
</Col>
{/* next_number */}
<Col>
<FormGroup
label={<T id={'next_number'} />}
className={'form-group--'}
intent={
errors.next_number && touched.next_number && Intent.DANGER
}
helperText={
<ErrorMessage name={'next_number'} {...{ errors, touched }} />
}
>
<InputGroup
intent={
errors.next_number && touched.next_number && Intent.DANGER
}
{...getFieldProps('next_number')}
/>
</FormGroup>
</Col>
</Row>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={onClose}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
type="submit"
disabled={isSubmitting}
>
<T id={'submit'} />
</Button>
</div>
</div>
</form>
</div>
);
}
export default compose(withSettingsActions)(ReferenceNumberForm);

View File

@@ -215,6 +215,8 @@ export default {
'The item category has been successfully created.',
the_item_category_has_been_successfully_edited:
'The item category has been successfully edited.',
the_item_category_has_been_successfully_deleted:
'The item category has been successfully deleted',
once_delete_these_views_you_will_not_able_restore_them:
"Once you delete the custom view, you won't be able to restore it later. Are you sure you want to delete this view?",
the_custom_view_has_been_successfully_deleted:
@@ -765,5 +767,8 @@ export default {
something_wentwrong: 'Something went wrong.',
new_password: 'New password',
license_code_: 'License code',
legal_organization_name: 'Legal Organization Name'
legal_organization_name: 'Legal Organization Name',
prefix: 'Prefix',
next_number: 'Next Number',
journal_number_settings: 'Journal Number Settings',
};

View File

@@ -1,159 +1,162 @@
.make-journal-entries{
.make-journal-entries {
padding-bottom: 20px;
display: flex;
flex-direction: column;
&__header{
&__header {
padding: 25px 27px 20px;
background: #fbfbfb;
.bp3-form-group{
.bp3-label{
.bp3-form-group {
.bp3-label {
font-weight: 500;
font-size: 13px;
color: #444;
}
}
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal).bp3-small {
font-size: 13px;
min-height: 33px;
margin-top: 21px;
margin-left: -30px;
}
}
&__table{
&__table {
padding: 15px 15px 0;
.bp3-form-group{
.bp3-form-group {
margin-bottom: 0;
}
.table{
.table {
border: 1px dotted rgb(195, 195, 195);
border-bottom: transparent;
border-left: transparent;
.th,
.td{
border-left: 1px dotted rgb(195, 195, 195);
.td {
border-left: 1px dotted rgb(195, 195, 195);
&.index{
&.index {
text-align: center;
span{
span {
width: 100%;
font-weight: 500;
}
}
}
}
.thead{
.tr .th{
.thead {
.tr .th {
padding: 10px 10px;
background-color: #F2F5FA;
background-color: #f2f5fa;
font-size: 14px;
font-weight: 500;
color: #1e1c3e;
&.index > div{
&.index > div {
width: 100%;
}
}
}
.tbody{
.tr .td{
.tbody {
.tr .td {
padding: 7px;
border-bottom: 1px dotted rgb(195, 195, 195);
min-height: 46px;
&.index{
background-color: #F2F5FA;
&.index {
background-color: #f2f5fa;
> span{
> span {
margin-top: auto;
margin-bottom: auto;
margin-bottom: auto;
}
}
}
.tr{
.tr {
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
.form-group--select-list .bp3-button{
border-color: #E5E5E5;
.form-group--select-list .bp3-button {
border-color: #e5e5e5;
border-radius: 3px;
padding-left: 8px;
padding-right: 8px;
}
.form-group--select-list{
&.bp3-intent-danger{
.bp3-button:not(.bp3-minimal){
.form-group--select-list {
&.bp3-intent-danger {
.bp3-button:not(.bp3-minimal) {
border-color: #db3737;
}
}
}
}
&:last-of-type{
.td{
&:last-of-type {
.td {
border-bottom: transparent;
.bp3-button,
.bp3-input-group{
.bp3-input-group {
display: none;
}
}
}
.td.actions{
.bp3-button{
.td.actions {
.bp3-button {
background-color: transparent;
color: #e66d6d;
&:hover{
&:hover {
color: #c23030;
}
svg{
svg {
color: inherit;
}
}
}
&.row--total{
&.row--total {
.account.td,
.debit.td,
.credit.td{
> span{
.credit.td {
> span {
padding-top: 2px;
}
}
.debit.td,
.credit.td{
> span{
.credit.td {
> span {
font-weight: 600;
color: #444;
}
}
}
.td{
&.note{
.bp3-form-group{
.td {
&.note {
.bp3-form-group {
width: 100%;
}
}
}
}
}
.th{
.th {
color: #444;
font-weight: 600;
border-bottom: 1px dotted #666;
}
.td{
.td {
border-bottom: 1px dotted #999;
}
.actions.td{
.bp3-button{
.actions.td {
.bp3-button {
background: transparent;
margin: 0;
}
@@ -161,23 +164,22 @@
}
}
.bp3-button{
&.button--clear-lines{
background-color: #FCEFEF;
.bp3-button {
&.button--clear-lines {
background-color: #fcefef;
}
}
.button--clear-lines,
.button--new-line{
.button--new-line {
padding-left: 14px;
padding-right: 14px;
}
.dropzone-container{
.dropzone-container {
margin-left: auto;
}
.dropzone{
.dropzone {
width: 300px;
height: 75px;