diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index b8ff6d95b..53fbf7034 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -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() { {/* */} + ); } diff --git a/client/src/containers/Accounting/MakeJournalEntriesFooter.js b/client/src/containers/Accounting/MakeJournalEntriesFooter.js index 1d8e0cb1a..d23186ebc 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesFooter.js +++ b/client/src/containers/Accounting/MakeJournalEntriesFooter.js @@ -6,10 +6,11 @@ export default function MakeJournalEntriesFooter({ formik: { isSubmitting }, onSubmitClick, onCancelClick, + manualJournal, }) { return (
- diff --git a/client/src/containers/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournalEntriesForm.js index 3f5d89176..5d7d0ddc6 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesForm.js +++ b/client/src/containers/Accounting/MakeJournalEntriesForm.js @@ -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} /> diff --git a/client/src/containers/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Accounting/MakeJournalEntriesHeader.js index 57bed5d86..ab9e33622 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesHeader.js +++ b/client/src/containers/Accounting/MakeJournalEntriesHeader.js @@ -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 (
@@ -62,7 +73,15 @@ export default function MakeJournalEntriesHeader({ /> - + + {/* */} +
); } + +export default compose(withDialogActions)(MakeJournalEntriesHeader); diff --git a/client/src/containers/Dialogs/ItemCategoryDialog.js b/client/src/containers/Dialogs/ItemCategoryDialog.js index 24f95e412..4cb1d8635 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog.js @@ -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') { diff --git a/client/src/containers/Dialogs/JournalNumberDailog.js b/client/src/containers/Dialogs/JournalNumberDailog.js new file mode 100644 index 000000000..71bd214b5 --- /dev/null +++ b/client/src/containers/Dialogs/JournalNumberDailog.js @@ -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 ( + } + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + onClose={handleClose} + > + + + ); +} + +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); diff --git a/client/src/containers/Expenses/ExpenseForm.js b/client/src/containers/Expenses/ExpenseForm.js index b659e8f00..20e75c8c6 100644 --- a/client/src/containers/Expenses/ExpenseForm.js +++ b/client/src/containers/Expenses/ExpenseForm.js @@ -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 (
diff --git a/client/src/containers/Expenses/ExpenseFormHeader.js b/client/src/containers/Expenses/ExpenseFormHeader.js index adff36a41..e15afc1f8 100644 --- a/client/src/containers/Expenses/ExpenseFormHeader.js +++ b/client/src/containers/Expenses/ExpenseFormHeader.js @@ -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 }) => ( + + ), + [], + ); + + // 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 (
@@ -105,16 +143,16 @@ function ExpenseFormHeader({ } > } - // itemRenderer={} - // itemPredicate={} + itemRenderer={CustomerRenderer} + itemPredicate={filterCustomer} popoverProps={{ minimal: true }} - // onItemSelect={} - selectedItem={values.beneficiary} - // selectedItemProp={'id'} - defaultText={} - labelProp={'beneficiary'} + onItemSelect={onChangeCustomer('customer_id')} + selectedItem={values.customer_id} + selectedItemProp={'id'} + defaultText={} + labelProp={'display_name'} /> @@ -235,4 +273,7 @@ export default compose( withCurrencies(({ currenciesList }) => ({ currenciesList, })), + withCustomers(({ customersItems }) => ({ + customersItems, + })), )(ExpenseFormHeader); diff --git a/client/src/containers/Expenses/Expenses.js b/client/src/containers/Expenses/Expenses.js index 11dedaf7a..46af4ab39 100644 --- a/client/src/containers/Expenses/Expenses.js +++ b/client/src/containers/Expenses/Expenses.js @@ -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); diff --git a/client/src/containers/JournalNumber/ReferenceNumberForm.js b/client/src/containers/JournalNumber/ReferenceNumberForm.js new file mode 100644 index 000000000..0c64ce65d --- /dev/null +++ b/client/src/containers/JournalNumber/ReferenceNumberForm.js @@ -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 ( +
+ +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce + tincidunt porta quam, +

+ + {/* prefix */} + + } + className={'form-group--'} + intent={errors.prefix && touched.prefix && Intent.DANGER} + helperText={ + + } + > + + + + {/* next_number */} + + } + className={'form-group--'} + intent={ + errors.next_number && touched.next_number && Intent.DANGER + } + helperText={ + + } + > + + + + +
+
+
+ + +
+
+ +
+ ); +} + +export default compose(withSettingsActions)(ReferenceNumberForm); diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 2fc8eb4c4..353323f53 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -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', }; diff --git a/client/src/style/pages/make-journal-entries.scss b/client/src/style/pages/make-journal-entries.scss index 89fa81c58..17458e862 100644 --- a/client/src/style/pages/make-journal-entries.scss +++ b/client/src/style/pages/make-journal-entries.scss @@ -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;