From 37f8662cc5d84975189948f4e3196f3834cf7579 Mon Sep 17 00:00:00 2001 From: elforjani13 <39470382+elforjani13@users.noreply.github.com> Date: Wed, 9 Mar 2022 20:59:19 +0200 Subject: [PATCH] feat(vendor): add vendor opening balance dialog. --- src/components/DialogsContainer.js | 4 + .../CustomerOpeningBalanceForm.js | 2 + .../VendorOpeningBalanceDialogContent.js | 25 ++++ .../VendorOpeningBalanceForm.js | 83 +++++++++++++ .../VendorOpeningBalanceForm.schema.js | 10 ++ .../VendorOpeningBalanceFormContent.js | 20 +++ .../VendorOpeningBalanceFormFields.js | 116 ++++++++++++++++++ ...VendorOpeningBalanceFormFloatingActions.js | 49 ++++++++ .../VendorOpeningBalanceFormProvider.js | 71 +++++++++++ .../VendorOpeningBalanceDialog/index.js | 39 ++++++ .../VendorOpeningBalanceDialog/utils.js | 20 +++ .../VendorDetailsActionsBar.js | 18 ++- .../Drawers/VendorDetailsDrawer/utils.js | 37 ++++++ src/hooks/query/vendors.js | 19 +++ src/lang/en/index.json | 14 ++- .../VendorOpeningBalance.scss | 19 +++ 16 files changed, 543 insertions(+), 3 deletions(-) create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceDialogContent.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.schema.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormContent.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFields.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFloatingActions.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormProvider.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/index.js create mode 100644 src/containers/Dialogs/VendorOpeningBalanceDialog/utils.js create mode 100644 src/containers/Drawers/VendorDetailsDrawer/utils.js create mode 100644 src/style/pages/VendorOpeningBalance/VendorOpeningBalance.scss diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js index 833016beb..8ddff3496 100644 --- a/src/components/DialogsContainer.js +++ b/src/components/DialogsContainer.js @@ -38,6 +38,8 @@ import WarehouseFormDialog from '../containers/Dialogs/WarehouseFormDialog'; import BranchFormDialog from '../containers/Dialogs/BranchFormDialog'; import BranchActivateDialog from '../containers/Dialogs/BranchActivateDialog'; import WarehouseActivateDialog from '../containers/Dialogs/WarehouseActivateDialog'; +import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog'; +import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog'; /** * Dialogs container. @@ -86,6 +88,8 @@ export default function DialogsContainer() { + + ); } diff --git a/src/containers/Dialogs/CustomerOpeningBalanceDialog/CustomerOpeningBalanceForm.js b/src/containers/Dialogs/CustomerOpeningBalanceDialog/CustomerOpeningBalanceForm.js index 2e9715b26..3a296fe3d 100644 --- a/src/containers/Dialogs/CustomerOpeningBalanceDialog/CustomerOpeningBalanceForm.js +++ b/src/containers/Dialogs/CustomerOpeningBalanceDialog/CustomerOpeningBalanceForm.js @@ -3,6 +3,7 @@ import moment from 'moment'; import intl from 'react-intl-universal'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; +import { defaultTo } from 'lodash'; import { AppToaster } from 'components'; import { CreateCustomerOpeningBalanceFormSchema } from './CustomerOpeningBalanceForm.schema'; @@ -35,6 +36,7 @@ function CustomerOpeningBalanceForm({ const initialValues = { ...defaultInitialValues, ...customer, + opening_balance: defaultTo(customer.opening_balance, ''), }; // Handles the form submit. diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceDialogContent.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceDialogContent.js new file mode 100644 index 000000000..19ae1055e --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceDialogContent.js @@ -0,0 +1,25 @@ +import React from 'react'; + +import 'style/pages/VendorOpeningBalance/VendorOpeningBalance.scss'; + +import VendorOpeningBalanceForm from './VendorOpeningBalanceForm'; +import { VendorOpeningBalanceFormProvider } from './VendorOpeningBalanceFormProvider'; + +/** + * Vendor Opening balance dialog content. + * @returns + */ +export default function VendorOpeningBalanceDialogContent({ + // #ownProps + dialogName, + vendorId, +}) { + return ( + + + + ); +} diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.js new file mode 100644 index 000000000..4b732b79e --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.js @@ -0,0 +1,83 @@ +import React from 'react'; +import moment from 'moment'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import { defaultTo } from 'lodash'; + +import { AppToaster } from 'components'; +import { CreateVendorOpeningBalanceFormSchema } from './VendorOpeningBalanceForm.schema'; +import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider'; + +import VendorOpeningBalanceFormContent from './VendorOpeningBalanceFormContent'; +import withDialogActions from 'containers/Dialog/withDialogActions'; + +import { compose } from 'utils'; + +const defaultInitialValues = { + opening_balance: '0', + opening_balance_branch_id: '', + opening_balance_exchange_rate: 1, + opening_balance_at: moment(new Date()).format('YYYY-MM-DD'), +}; + +/** + * Vendor Opening balance form. + * @returns + */ +function VendorOpeningBalanceForm({ + // #withDialogActions + closeDialog, +}) { + const { dialogName, vendor, editVendorOpeningBalanceMutate } = + useVendorOpeningBalanceContext(); + + // Initial form values + const initialValues = { + ...defaultInitialValues, + ...vendor, + opening_balance: defaultTo(vendor.opening_balance, ''), + + }; + + // Handles the form submit. + const handleFormSubmit = (values, { setSubmitting, setErrors }) => { + const formValues = { + ...values, + }; + + // Handle request response success. + const onSuccess = (response) => { + AppToaster.show({ + message: intl.get('vendor_opening_balance.success_message'), + intent: Intent.SUCCESS, + }); + closeDialog(dialogName); + }; + + // Handle request response errors. + const onError = ({ + response: { + data: { errors }, + }, + }) => { + if (errors) { + } + setSubmitting(false); + }; + + editVendorOpeningBalanceMutate([vendor.id, formValues]) + .then(onSuccess) + .catch(onError); + }; + + return ( + + ); +} +export default compose(withDialogActions)(VendorOpeningBalanceForm); diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.schema.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.schema.js new file mode 100644 index 000000000..5435aa0b7 --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceForm.schema.js @@ -0,0 +1,10 @@ +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + opening_balance_branch_id: Yup.string(), + opening_balance: Yup.number().nullable(), + opening_balance_at: Yup.date(), + opening_balance_exchange_rate: Yup.number(), +}); + +export const CreateVendorOpeningBalanceFormSchema = Schema; diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormContent.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormContent.js new file mode 100644 index 000000000..4cdca9151 --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormContent.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Form } from 'formik'; + +import VendorOpeningBalanceFormFields from './VendorOpeningBalanceFormFields'; +import VendorOpeningBalanceFormFloatingActions from './VendorOpeningBalanceFormFloatingActions'; + +/** + * Vendor Opening balance form content. + * @returns + */ +function VendorOpeningBalanceFormContent() { + return ( +
+ + + + ); +} + +export default VendorOpeningBalanceFormContent; diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFields.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFields.js new file mode 100644 index 000000000..7f885366c --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFields.js @@ -0,0 +1,116 @@ +import React from 'react'; +import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core'; +import { DateInput } from '@blueprintjs/datetime'; +import { isEqual } from 'lodash'; +import { FastField, useFormikContext } from 'formik'; +import { momentFormatter, tansformDateValue, handleDateChange } from 'utils'; +import { Features } from 'common'; +import classNames from 'classnames'; + +import { + If, + Icon, + FormattedMessage as T, + ExchangeRateMutedField, + BranchSelect, + BranchSelectButton, + FeatureCan, + InputPrependText, +} from 'components'; +import { FMoneyInputGroup, FFormGroup } from '../../../components/Forms'; + +import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider'; +import { useSetPrimaryBranchToForm } from './utils'; + +import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; +import { compose } from 'utils'; + +/** + * Vendor Opening balance form fields. + * @returns + */ +function VendorOpeningBalanceFormFields({ + // #withCurrentOrganization + organization: { base_currency }, +}) { + // Formik context. + const { values } = useFormikContext(); + + const { branches, vendor } = useVendorOpeningBalanceContext(); + + // Sets the primary branch to form. + useSetPrimaryBranchToForm(); + + return ( +
+ {/*------------ Opening balance -----------*/} + } + > + + + + + + + {/*------------ Opening balance at -----------*/} + + {({ form, field: { value } }) => ( + } + className={Classes.FILL} + > + { + form.setFieldValue('opening_balance_at', formattedDate); + })} + value={tansformDateValue(value)} + popoverProps={{ position: Position.BOTTOM, minimal: true }} + inputProps={{ + leftIcon: , + }} + /> + + )} + + + + {/*------------ Opening balance exchange rate -----------*/} + + + + {/*------------ Opening balance branch id -----------*/} + + } + name={'opening_balance_branch_id'} + className={classNames('form-group--select-list', Classes.FILL)} + > + + + +
+ ); +} + +export default compose(withCurrentOrganization())( + VendorOpeningBalanceFormFields, +); diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFloatingActions.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFloatingActions.js new file mode 100644 index 000000000..cfbf2812a --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormFloatingActions.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Intent, Button, Classes } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; +import { FormattedMessage as T } from 'components'; + +import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +/** + * Vendor Opening balance floating actions. + * @returns + */ +function VendorOpeningBalanceFormFloatingActions({ + // #withDialogActions + closeDialog, +}) { + // dialog context. + const { dialogName } = useVendorOpeningBalanceContext(); + + // Formik context. + const { isSubmitting } = useFormikContext(); + + // Handle close button click. + const handleCancelBtnClick = () => { + closeDialog(dialogName); + }; + + return ( +
+
+ + +
+
+ ); +} +export default compose(withDialogActions)( + VendorOpeningBalanceFormFloatingActions, +); diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormProvider.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormProvider.js new file mode 100644 index 000000000..4f0aa7b4b --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/VendorOpeningBalanceFormProvider.js @@ -0,0 +1,71 @@ +import React from 'react'; +import { DialogContent } from 'components'; +import { + useBranches, + useVendor, + useEditVendorOpeningBalance, +} from 'hooks/query'; +import { useFeatureCan } from 'hooks/state'; +import { Features } from 'common'; +import { pick, defaultTo } from 'lodash'; + +const VendorOpeningBalanceContext = React.createContext(); + +/** + * Vendor Opening balance provider. + * @returns + */ +function VendorOpeningBalanceFormProvider({ + query, + vendorId, + dialogName, + ...props +}) { + // Features guard. + const { featureCan } = useFeatureCan(); + const isBranchFeatureCan = featureCan(Features.Branches); + + const { mutateAsync: editVendorOpeningBalanceMutate } = + useEditVendorOpeningBalance(); + + // Fetches the branches list. + const { + data: branches, + isLoading: isBranchesLoading, + isSuccess: isBranchesSuccess, + } = useBranches(query, { enabled: isBranchFeatureCan }); + + // Handle fetch vendor details. + const { data: vendor, isLoading: isVendorLoading } = useVendor(vendorId, { + enabled: !!vendorId, + }); + + // State provider. + const provider = { + branches, + vendor: { + ...pick(vendor, [ + 'id', + 'opening_balance', + 'opening_balance_exchange_rate', + 'currency_code', + ]), + }, + + isBranchesSuccess, + isBranchesLoading, + dialogName, + editVendorOpeningBalanceMutate, + }; + + return ( + + + + ); +} + +const useVendorOpeningBalanceContext = () => + React.useContext(VendorOpeningBalanceContext); + +export { VendorOpeningBalanceFormProvider, useVendorOpeningBalanceContext }; diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/index.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/index.js new file mode 100644 index 000000000..08fe52fac --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/index.js @@ -0,0 +1,39 @@ +import React from 'react'; + +import { FormattedMessage as T } from 'components'; +import { Dialog, DialogSuspense } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose } from 'redux'; + +const VendorOpeningBalanceDialogContent = React.lazy(() => + import('./VendorOpeningBalanceDialogContent'), +); + +/** + * Vendor Opening balance dialog. + * @returns + */ +function VendorOpeningBalanceDialog({ + dialogName, + payload: { vendorId }, + isOpen, +}) { + return ( + } + isOpen={isOpen} + canEscapeJeyClose={true} + autoFocus={true} + className={'dialog--vendor-opening-balance'} + > + + + + + ); +} +export default compose(withDialogRedux())(VendorOpeningBalanceDialog); diff --git a/src/containers/Dialogs/VendorOpeningBalanceDialog/utils.js b/src/containers/Dialogs/VendorOpeningBalanceDialog/utils.js new file mode 100644 index 000000000..8b534e87e --- /dev/null +++ b/src/containers/Dialogs/VendorOpeningBalanceDialog/utils.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { useFormikContext } from 'formik'; +import { first } from 'lodash'; + +import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider'; + +export const useSetPrimaryBranchToForm = () => { + const { setFieldValue } = useFormikContext(); + const { branches, isBranchesSuccess } = useVendorOpeningBalanceContext(); + + React.useEffect(() => { + if (isBranchesSuccess) { + const primaryBranch = branches.find((b) => b.primary) || first(branches); + + if (primaryBranch) { + setFieldValue('opening_balance_branch_id', primaryBranch.id); + } + } + }, [isBranchesSuccess, setFieldValue, branches]); +}; diff --git a/src/containers/Drawers/VendorDetailsDrawer/VendorDetailsActionsBar.js b/src/containers/Drawers/VendorDetailsDrawer/VendorDetailsActionsBar.js index 86854bda2..82d1c2967 100644 --- a/src/containers/Drawers/VendorDetailsDrawer/VendorDetailsActionsBar.js +++ b/src/containers/Drawers/VendorDetailsDrawer/VendorDetailsActionsBar.js @@ -19,8 +19,10 @@ import { useVendorDetailsDrawerContext } from './VendorDetailsDrawerProvider'; import withAlertsActions from 'containers/Alert/withAlertActions'; import withDrawerActions from 'containers/Drawer/withDrawerActions'; +import withDialogActions from 'containers/Dialog/withDialogActions'; import { Can, Icon, FormattedMessage as T } from 'components'; +import { VendorMoreMenuItem } from './utils'; import { AbilitySubject, SaleInvoiceAction, @@ -33,13 +35,16 @@ import { safeCallback, compose } from 'utils'; * Vendor details actions bar. */ function VendorDetailsActionsBar({ + // #withDialogActions + openDialog, + // #withAlertsActions openAlert, // #withDrawerActions closeDrawer, }) { - const { vendor, vendorId } = useVendorDetailsDrawerContext(); + const { vendorId } = useVendorDetailsDrawerContext(); const history = useHistory(); // Handle edit vendor. @@ -63,6 +68,10 @@ function VendorDetailsActionsBar({ closeDrawer('vendor-details-drawer'); }; + const handleEditOpeningBalance = () => { + openDialog('vendor-opening-balance', { vendorId }); + }; + return ( @@ -112,6 +121,12 @@ function VendorDetailsActionsBar({ onClick={safeCallback(onDeleteContact)} /> + + ); @@ -120,4 +135,5 @@ function VendorDetailsActionsBar({ export default compose( withDrawerActions, withAlertsActions, + withDialogActions, )(VendorDetailsActionsBar); diff --git a/src/containers/Drawers/VendorDetailsDrawer/utils.js b/src/containers/Drawers/VendorDetailsDrawer/utils.js new file mode 100644 index 000000000..0849640c0 --- /dev/null +++ b/src/containers/Drawers/VendorDetailsDrawer/utils.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { + Button, + Popover, + PopoverInteractionKind, + Position, + MenuItem, + Menu, +} from '@blueprintjs/core'; +import { Icon, FormattedMessage as T } from 'components'; + +/** + * Vendor more actions menu items. + * @param {*} param0 + */ +export function VendorMoreMenuItem({ payload: { onEditOpeningBalance } }) { + return ( + + } + onClick={onEditOpeningBalance} + /> + + } + > +