diff --git a/src/common/abilityOption.js b/src/common/abilityOption.js index 1a5625ca3..61c128c5b 100644 --- a/src/common/abilityOption.js +++ b/src/common/abilityOption.js @@ -46,7 +46,7 @@ export const SaleInvoiceAction = { Create: 'Create', Edit: 'Edit', Delete: 'Delete', - Writeoff: 'Writeoff', + Writeoff: 'bad-debt', NotifyBySms: 'NotifyBySms', }; diff --git a/src/common/classes.js b/src/common/classes.js index e1df0b33f..6b0872396 100644 --- a/src/common/classes.js +++ b/src/common/classes.js @@ -39,6 +39,8 @@ const CLASSES = { PAGE_FORM_ITEM: 'page-form--item', PAGE_FORM_MAKE_JOURNAL: 'page-form--make-journal-entries', PAGE_FORM_EXPENSE: 'page-form--expense', + PAGE_FORM_CREDIT_NOTE:'page-form--credit-note', + PAGE_FORM_VENDOR_CREDIT_NOTE:'page-form--vendor-credit-note', FORM_GROUP_LIST_SELECT: 'form-group--select-list', diff --git a/src/common/drawers.js b/src/common/drawers.js index 3a1b21032..b42c44b9f 100644 --- a/src/common/drawers.js +++ b/src/common/drawers.js @@ -14,4 +14,6 @@ export const DRAWERS = { QUICK_WRITE_VENDOR: 'quick-write-vendor', QUICK_CREATE_CUSTOMER: 'quick-create-customer', QUICK_CREATE_ITEM: 'quick-create-item', + CREDIT_NOTE_DETAIL_DRAWER: 'credit-note-detail-drawer', + VENDOR_CREDIT_DETAIL_DRAWER: 'vendor-credit-detail-drawer', }; diff --git a/src/common/resourcesTypes.js b/src/common/resourcesTypes.js index 12ec85401..91e981aa8 100644 --- a/src/common/resourcesTypes.js +++ b/src/common/resourcesTypes.js @@ -11,4 +11,6 @@ export const RESOURCES_TYPES = { EXPENSE: 'expense', MANUAL_JOURNAL: 'manual_journal', ACCOUNT: 'account', + CREDIT_NOTE: 'credit_note', + VENDOR_CREDIT:'vendor_credit' }; diff --git a/src/common/tables.js b/src/common/tables.js index 9acd9bd5c..59d6ca127 100644 --- a/src/common/tables.js +++ b/src/common/tables.js @@ -14,6 +14,8 @@ export const TABLES = { EXPENSES: 'expenses', CASHFLOW_ACCOUNTS: 'cashflow_accounts', CASHFLOW_Transactions: 'cashflow_transactions', + CREDIT_NOTES: 'credit_notes', + VENDOR_CREDITS: 'vendor_credits', }; export const TABLE_SIZE = { diff --git a/src/components/DrawersContainer.js b/src/components/DrawersContainer.js index 135dcb8a3..b7c0e8fe6 100644 --- a/src/components/DrawersContainer.js +++ b/src/components/DrawersContainer.js @@ -17,6 +17,8 @@ import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTrans import QuickCreateCustomerDrawer from '../containers/Drawers/QuickCreateCustomerDrawer'; import QuickCreateItemDrawer from '../containers/Drawers/QuickCreateItemDrawer'; import QuickWriteVendorDrawer from '../containers/Drawers/QuickWriteVendorDrawer'; +import CreditNoteDetailDrawer from '../containers/Drawers/CreditNoteDetailDrawer'; +import VendorCreditDetailDrawer from '../containers/Drawers/VendorCreditDetailDrawer'; import { DRAWERS } from 'common/drawers'; @@ -47,6 +49,8 @@ export default function DrawersContainer() { + + ); } diff --git a/src/config/sidebarMenu.js b/src/config/sidebarMenu.js index a847f8e52..7a904a1fb 100644 --- a/src/config/sidebarMenu.js +++ b/src/config/sidebarMenu.js @@ -158,6 +158,10 @@ export default [ ability: SaleReceiptAction.View, }, }, + { + text: , + href: '/credit-notes', + }, { text: , href: '/payment-receives', @@ -233,6 +237,10 @@ export default [ ability: SaleReceiptAction.Create, }, }, + { + text: , + href: '/credit-notes/new', + }, { text: , href: '/payment-receives/new', @@ -254,6 +262,10 @@ export default [ ability: BillAction.View, }, }, + { + text: , + href: '/vendor-credits', + }, { text: , href: '/payment-mades', @@ -298,6 +310,14 @@ export default [ ability: BillAction.Create, }, }, + { + text: , + href: '/vendor-credits/new', + permission: { + subject: AbilitySubject.Bill, + ability: BillAction.Create, + }, + }, { text: , href: '/payment-mades/new', @@ -405,14 +425,14 @@ export default [ ability: ManualJournalAction.View, }, }, - { - text: , - href: '/transactions-locking', - permission: { - subject: AbilitySubject.ManualJournal, - ability: ManualJournalAction.TransactionLocking, - }, - }, + // { + // text: , + // href: '/transactions-locking', + // permission: { + // subject: AbilitySubject.ManualJournal, + // ability: ManualJournalAction.TransactionLocking, + // }, + // }, { text: , href: '/exchange-rates', diff --git a/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js b/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js new file mode 100644 index 000000000..be664372a --- /dev/null +++ b/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js @@ -0,0 +1,80 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; +import withDrawerActions from 'containers/Drawer/withDrawerActions'; + +import { useDeleteCreditNote } from 'hooks/query'; +import { compose } from 'utils'; + +/** + * Credit note delete alert. + */ +function CreditNoteDeleteAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { creditNoteId }, + + // #withAlertActions + closeAlert, + + // #withDrawerActions + closeDrawer, +}) { + const { isLoading, mutateAsync: deleteCreditNoteMutate } = + useDeleteCreditNote(); + + // handle cancel delete credit note alert. + const handleCancelDeleteAlert = () => { + closeAlert(name); + }; + const handleConfirmCreditNoteDelete = () => { + deleteCreditNoteMutate(creditNoteId) + .then(() => { + AppToaster.show({ + message: intl.get('credit_note.alert.success_message'), + intent: Intent.SUCCESS, + }); + closeDrawer('credit-note-detail-drawer'); + }) + .catch( + ({ + response: { + data: { errors }, + }, + }) => {}, + ) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancelDeleteAlert} + onConfirm={handleConfirmCreditNoteDelete} + loading={isLoading} + > +

+ +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, + withDrawerActions, +)(CreditNoteDeleteAlert); diff --git a/src/containers/Alerts/VendorCeditNotes/VendorCreditDeleteAlert.js b/src/containers/Alerts/VendorCeditNotes/VendorCreditDeleteAlert.js new file mode 100644 index 000000000..160905c1d --- /dev/null +++ b/src/containers/Alerts/VendorCeditNotes/VendorCreditDeleteAlert.js @@ -0,0 +1,82 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import { FormattedMessage as T, FormattedHTMLMessage } from 'components'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; +import withDrawerActions from 'containers/Drawer/withDrawerActions'; + +import { useDeleteVendorCredit } from 'hooks/query'; +import { compose } from 'utils'; + +/** + * Vendor Credit delete alert. + */ +function VendorCreditDeleteAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { vendorCreditId }, + + // #withAlertActions + closeAlert, + + // #withDrawerActions + closeDrawer, +}) { + const { isLoading, mutateAsync: deleteVendorCreditMutate } = + useDeleteVendorCredit(); + + // handle cancel delete credit note alert. + const handleCancelDeleteAlert = () => { + closeAlert(name); + }; + const handleConfirmCreditDelete = () => { + deleteVendorCreditMutate(vendorCreditId) + .then(() => { + AppToaster.show({ + message: intl.get('vendor_credits.alert.success_message'), + intent: Intent.SUCCESS, + }); + closeDrawer('vendor-credit-detail-drawer'); + }) + .catch( + ({ + response: { + data: { errors }, + }, + }) => {}, + ) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancelDeleteAlert} + onConfirm={handleConfirmCreditDelete} + loading={isLoading} + > +

+ +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, + withDrawerActions, +)(VendorCreditDeleteAlert); diff --git a/src/containers/AlertsContainer/registered.js b/src/containers/AlertsContainer/registered.js index d0e77fcfb..bbab107a3 100644 --- a/src/containers/AlertsContainer/registered.js +++ b/src/containers/AlertsContainer/registered.js @@ -17,6 +17,8 @@ import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTr import UsersAlerts from '../Preferences/Users/UsersAlerts'; import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts'; import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts'; +import CreditNotesAlerts from '../Sales/CreditNotes/CreditNotesAlerts'; +import VendorCreditNotesAlerts from '../Purchases/CreditNotes/VendorCreditNotesAlerts'; export default [ ...AccountsAlerts, @@ -38,4 +40,6 @@ export default [ ...UsersAlerts, ...CurrenciesAlerts, ...RolesAlerts, + ...CreditNotesAlerts, + ...VendorCreditNotesAlerts, ]; diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js index 4861a65eb..ccc45719b 100644 --- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js +++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js @@ -90,8 +90,10 @@ export default function AllocateLandedCostFormFields() { selectedItem={value} selectedItemProp={'id'} textProp={'name'} - labelProp={'id'} - defaultText={intl.get('Select transaction')} + labelProp={'formatted_unallocated_cost_amount'} + defaultText={intl.get( + 'landed_cost.dialog.label_select_transaction', + )} popoverProps={{ minimal: true }} /> @@ -129,7 +131,10 @@ export default function AllocateLandedCostFormFields() { selectedItem={value} selectedItemProp={'id'} textProp={'name'} - defaultText={intl.get('Select transaction entry')} + labelProp={'formatted_unallocated_cost_amount'} + defaultText={intl.get( + 'landed_cost.dialog.label_select_transaction_entry', + )} popoverProps={{ minimal: true }} /> diff --git a/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogContent.js b/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogContent.js new file mode 100644 index 000000000..ff1d56f16 --- /dev/null +++ b/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogContent.js @@ -0,0 +1,102 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import { useSaveSettings } from 'hooks/query'; + +import { CreditNoteNumberDialogProvider } from './CreditNoteNumberDialogProvider'; +import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import withSettings from 'containers/Settings/withSettings'; +import withSettingsActions from 'containers/Settings/withSettingsActions'; +import { compose } from 'utils'; +import { + transformFormToSettings, + transformSettingsToForm, +} from 'containers/JournalNumber/utils'; + +/** + * credit note number dialog content + */ +function CreditNoteNumberDialogContent({ + // #ownProps + initialValues, + onConfirm, + + // #withSettings + nextNumber, + numberPrefix, + autoIncrement, + + // #withDialogActions + closeDialog, +}) { + const { mutateAsync: saveSettings } = useSaveSettings(); + const [referenceFormValues, setReferenceFormValues] = React.useState(null); + + // Handle the submit form. + const handleSubmitForm = (values, { setSubmitting }) => { + // Handle the form success. + const handleSuccess = () => { + setSubmitting(false); + closeDialog('credit-number-form'); + onConfirm(values); + }; + // Handle the form errors. + const handleErrors = () => { + setSubmitting(false); + }; + if (values.incrementMode === 'manual-transaction') { + handleSuccess(); + return; + } + // Transformes the form values to settings to save it. + const options = transformFormToSettings(values, 'credit_note'); + + // Save the settings. + saveSettings({ options }).then(handleSuccess).catch(handleErrors); + }; + + // Handle the dialog close. + const handleClose = () => { + closeDialog('credit-number-form'); + }; + // Handle form change. + const handleChange = (values) => { + setReferenceFormValues(values); + }; + + // Description. + const description = + referenceFormValues?.incrementMode === 'auto' + ? intl.get('credit_note.auto_increment.auto') + : intl.get('credit_note.auto_increment.manually'); + + return ( + + + + ); +} + +export default compose( + withDialogActions, + withSettingsActions, + withSettings(({ creditNoteSettings }) => ({ + autoIncrement: creditNoteSettings?.autoIncrement, + nextNumber: creditNoteSettings?.nextNumber, + numberPrefix: creditNoteSettings?.numberPrefix, + })), +)(CreditNoteNumberDialogContent); diff --git a/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogProvider.js b/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogProvider.js new file mode 100644 index 000000000..94daf467f --- /dev/null +++ b/src/containers/Dialogs/CreditNoteNumberDialog/CreditNoteNumberDialogProvider.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { DialogContent } from 'components'; +import { useSettingsCreditNotes } from 'hooks/query'; + +const CreditNoteNumberDialogContext = React.createContext(); + +/** + *Credit Note number dialog provider + */ +function CreditNoteNumberDialogProvider({ query, ...props }) { + const { isLoading: isSettingsLoading } = useSettingsCreditNotes(); + + // Provider payload. + const provider = { + isSettingsLoading, + }; + + return ( + + + + ); +} + +const useCreditNoteNumberDialogContext = () => + React.useContext(CreditNoteNumberDialogContext); + +export { CreditNoteNumberDialogProvider, useCreditNoteNumberDialogContext }; diff --git a/src/containers/Dialogs/CreditNoteNumberDialog/index.js b/src/containers/Dialogs/CreditNoteNumberDialog/index.js new file mode 100644 index 000000000..c1786c324 --- /dev/null +++ b/src/containers/Dialogs/CreditNoteNumberDialog/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Dialog, DialogSuspense, FormattedMessage as T } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose, saveInvoke } from 'utils'; + +const CreditNoteNumberDialogContent = React.lazy(() => + import('./CreditNoteNumberDialogContent'), +); + +/** + * Credit note number dialog. + */ +function CreditNoteNumberDialog({ + dialogName, + payload: { initialFormValues }, + isOpen, + onConfirm, +}) { + const handleConfirm = (values) => { + saveInvoke(onConfirm, values); + }; + + return ( + } + name={dialogName} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + > + + + + + ); +} +export default compose(withDialogRedux())(CreditNoteNumberDialog); diff --git a/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js b/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js index c66fe492f..9e10bf849 100644 --- a/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js +++ b/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js @@ -24,7 +24,7 @@ function InviteUserFormContent({ const handleClose = () => { closeDialog(dialogName); }; - console.log(roles, 'XX'); + return (
diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js new file mode 100644 index 000000000..b9dcecc61 --- /dev/null +++ b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js @@ -0,0 +1,102 @@ +import React from 'react'; +import intl from 'react-intl-universal'; +import { useSaveSettings } from 'hooks/query'; + +import { VendorCreditNumberDilaogProvider } from './VendorCreditNumberDilaogProvider'; +import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import withSettings from 'containers/Settings/withSettings'; +import withSettingsActions from 'containers/Settings/withSettingsActions'; +import { compose } from 'utils'; +import { + transformFormToSettings, + transformSettingsToForm, +} from 'containers/JournalNumber/utils'; + +/** + * Vendor credit number dialog + */ +function VendorCreditNumberDialogContent({ + // #ownProps + initialValues, + onConfirm, + + // #withSettings + nextNumber, + numberPrefix, + autoIncrement, + + // #withDialogActions + closeDialog, +}) { + const { mutateAsync: saveSettings } = useSaveSettings(); + const [referenceFormValues, setReferenceFormValues] = React.useState(null); + + // Handle the submit form. + const handleSubmitForm = (values, { setSubmitting }) => { + // Handle the form success. + const handleSuccess = () => { + setSubmitting(false); + closeDialog('vendor-credit-form'); + onConfirm(values); + }; + // Handle the form errors. + const handleErrors = () => { + setSubmitting(false); + }; + if (values.incrementMode === 'manual-transaction') { + handleSuccess(); + return; + } + // Transformes the form values to settings to save it. + const options = transformFormToSettings(values, 'vendor_credit'); + + // Save the settings. + saveSettings({ options }).then(handleSuccess).catch(handleErrors); + }; + + // Handle the dialog close. + const handleClose = () => { + closeDialog('vendor-credit-form'); + }; + // Handle form change. + const handleChange = (values) => { + setReferenceFormValues(values); + }; + + // Description. + const description = + referenceFormValues?.incrementMode === 'auto' + ? intl.get('vendor_credit.auto_increment.auto') + : intl.get('vendor_credit.auto_increment.manually'); + + return ( + + + + ); +} + +export default compose( + withDialogActions, + withSettingsActions, + withSettings(({ vendorsCreditNoteSetting }) => ({ + autoIncrement: vendorsCreditNoteSetting?.autoIncrement, + nextNumber: vendorsCreditNoteSetting?.nextNumber, + numberPrefix: vendorsCreditNoteSetting?.numberPrefix, + })), +)(VendorCreditNumberDialogContent); diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js new file mode 100644 index 000000000..0d285b6fc --- /dev/null +++ b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { DialogContent } from 'components'; +import { useSettingsVendorCredits } from 'hooks/query'; + +const VendorCreditNumberDialogContext = React.createContext(); + +/** + * Vendor credit number dialog provider + */ +function VendorCreditNumberDilaogProvider({ query, ...props }) { + const { isLoading: isSettingsLoading } = useSettingsVendorCredits(); + + // Provider payload. + const provider = { + isSettingsLoading, + }; + + return ( + + + + ); +} + +const useVendorCreditNumberDialogContext = () => + React.useContext(VendorCreditNumberDialogContext); + +export { VendorCreditNumberDilaogProvider, useVendorCreditNumberDialogContext }; diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/index.js b/src/containers/Dialogs/VendorCreditNumberDialog/index.js new file mode 100644 index 000000000..e92fdf702 --- /dev/null +++ b/src/containers/Dialogs/VendorCreditNumberDialog/index.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Dialog, DialogSuspense, FormattedMessage as T } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose, saveInvoke } from 'utils'; + +const VendorCreditNumberDialogContent = React.lazy(() => + import('./VendorCreditNumberDialogContent'), +); + +/** + * Vendor Credit number dialog. + */ +function VendorCreditNumberDialog({ + dialogName, + payload: { initialFormValues }, + isOpen, + onConfirm, +}) { + const handleConfirm = (values) => { + saveInvoke(onConfirm, values); + }; + + return ( + } + name={dialogName} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + > + + + + + ); +} +export default compose(withDialogRedux())(VendorCreditNumberDialog); diff --git a/src/containers/Drawers/BillDrawer/BillDrawerContent.js b/src/containers/Drawers/BillDrawer/BillDrawerContent.js index c5458fdc5..e2df6d6c4 100644 --- a/src/containers/Drawers/BillDrawer/BillDrawerContent.js +++ b/src/containers/Drawers/BillDrawer/BillDrawerContent.js @@ -1,7 +1,6 @@ import React from 'react'; import { DrawerBody } from 'components'; -import 'style/components/Drawers/ViewDetail/ViewDetail.scss'; import { BillDrawerProvider } from './BillDrawerProvider'; import BillDrawerDetails from './BillDrawerDetails'; diff --git a/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js b/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js index c06848a30..5830a0166 100644 --- a/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js +++ b/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js @@ -5,6 +5,8 @@ import { Button, Classes, NavbarGroup } from '@blueprintjs/core'; import { useLocatedLandedCostColumns, ActionsMenu } from './components'; import { useBillDrawerContext } from './BillDrawerProvider'; +import '../../../style/pages/AllocateLandedCost/List.scss'; + import withAlertsActions from 'containers/Alert/withAlertActions'; import withDialogActions from 'containers/Dialog/withDialogActions'; import withDrawerActions from 'containers/Drawer/withDrawerActions'; diff --git a/src/containers/Drawers/BillDrawer/components.js b/src/containers/Drawers/BillDrawer/components.js index 2a4d29f93..184a120f0 100644 --- a/src/containers/Drawers/BillDrawer/components.js +++ b/src/containers/Drawers/BillDrawer/components.js @@ -1,5 +1,6 @@ import React from 'react'; import intl from 'react-intl-universal'; +import styled from 'styled-components'; import { Intent, MenuItem, Menu } from '@blueprintjs/core'; import { safeCallback } from 'utils'; import { Icon } from 'components'; @@ -24,7 +25,7 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) { */ export function FromTransactionCell({ row: { original }, - payload: { onFromTranscationClick } + payload: { onFromTranscationClick }, }) { // Handle the link click const handleAnchorClick = () => { @@ -38,6 +39,18 @@ export function FromTransactionCell({ ); } +/** + * Name accessor. + */ +export const NameAccessor = (row) => { + return ( + + {row.name} + {row.description} + + ); +}; + /** * Retrieve bill located landed cost table columns. */ @@ -46,7 +59,7 @@ export function useLocatedLandedCostColumns() { () => [ { Header: intl.get('name'), - accessor: 'description', + accessor: NameAccessor, width: 150, className: 'name', }, @@ -65,7 +78,7 @@ export function useLocatedLandedCostColumns() { }, { Header: intl.get('allocation_method'), - accessor: 'allocation_method_formatted', + accessor: 'allocation_method', width: 100, className: 'allocation-method', }, @@ -73,3 +86,12 @@ export function useLocatedLandedCostColumns() { [], ); } + +const LabelName = styled.div``; + +const LabelDescription = styled.div` + font-size: 12px; + margin-top: 6px; + display: block; + opacity: 0.75; +`; diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js new file mode 100644 index 000000000..74f7e760b --- /dev/null +++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { Tab } from '@blueprintjs/core'; +import intl from 'react-intl-universal'; +import { DrawerMainTabs } from 'components'; + +import CreditNoteDetailPanel from './CreditNoteDetailPanel'; +import clsx from 'classnames'; + +import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss'; + +/** + * Credit Note view detail. + */ +export default function CreditNoteDetail() { + return ( +
+ + } + /> + +
+ ); +} diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js new file mode 100644 index 000000000..a7b92ae21 --- /dev/null +++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; + +import { + Button, + NavbarGroup, + Classes, + NavbarDivider, + Intent, +} from '@blueprintjs/core'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import withAlertsActions from 'containers/Alert/withAlertActions'; +import withDrawerActions from 'containers/Drawer/withDrawerActions'; + +import { Icon, FormattedMessage as T, MoreMenuItems, Can } from 'components'; + +import { compose } from 'utils'; + +/** + * Credit note detail actions bar. + */ +function CreditNoteDetailActionsBar({ + // #withDialogActions + openDialog, + + // #withAlertsActions + openAlert, + + // #withDrawerActions + closeDrawer, +}) { + const { creditNoteId } = useCreditNoteDetailDrawerContext(); + + const history = useHistory(); + + // Handle edit credit note. + const handleEditCreditNote = () => { + history.push(`/credit-notes/${creditNoteId}/edit`); + closeDrawer('credit-note-detail-drawer'); + }; + + // Handle delete credit note. + const handleDeleteCreditNote = () => { + openAlert('credit-note-delete', { creditNoteId }); + }; + + return ( + + +
+ ); +} diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js new file mode 100644 index 000000000..05fed2f74 --- /dev/null +++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js @@ -0,0 +1,169 @@ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { Formik, Form } from 'formik'; +import { Button, Intent } from '@blueprintjs/core'; +import intl from 'react-intl-universal'; +import { sumBy, omit, isEmpty } from 'lodash'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; +import { + CreateCreditNoteFormSchema, + EditCreditNoteFormSchema, +} from './VendorCreditNoteForm.schema'; + +import VendorCreditNoteFormHeader from './VendorCreditNoteFormHeader'; +import VendorCreditNoteItemsEntriesEditor from './VendorCreditNoteItemsEntriesEditor'; +import VendorCreditNoteFormFooter from './VendorCreditNoteFormFooter'; +import VendorCreditNoteFloatingActions from './VendorCreditNoteFloatingActions'; +import VendorCreditNoteFormDialogs from './VendorCreditNoteFormDialogs'; + +import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider'; + +import { AppToaster } from 'components'; +import { compose, safeSumBy, transactionNumber } from 'utils'; +import { + defaultVendorsCreditNote, + filterNonZeroEntries, + transformToEditForm, + transformFormValuesToRequest, +} from './utils'; + +import withSettings from 'containers/Settings/withSettings'; +import withCurrentOrganization from 'containers/Organization/withCurrentOrganization'; + +/** + * Vendor Credit note form. + */ +function VendorCreditNoteForm({ + // #withSettings + vendorcreditAutoIncrement, + vendorcreditNumberPrefix, + vendorcreditNextNumber, + + // #withCurrentOrganization + organization: { base_currency }, +}) { + const history = useHistory(); + + // Vendor Credit note form context. + const { + isNewMode, + submitPayload, + vendorCredit, + createVendorCreditMutate, + editVendorCreditMutate, + } = useVendorCreditNoteFormContext(); + + // Credit number. + const vendorCreditNumber = transactionNumber( + vendorcreditNumberPrefix, + vendorcreditNextNumber, + ); + + // Initial values. + const initialValues = React.useMemo( + () => ({ + ...(!isEmpty(vendorCredit) + ? { + ...transformToEditForm(vendorCredit), + } + : { + ...defaultVendorsCreditNote, + ...(vendorcreditAutoIncrement && { + vendor_credit_number: vendorCreditNumber, + }), + }), + }), + [vendorCredit, base_currency], + ); + + // Handles form submit. + const handleFormSubmit = ( + values, + { setSubmitting, setErrors, resetForm }, + ) => { + const entries = filterNonZeroEntries(values.entries); + const totalQuantity = safeSumBy(entries, 'quantity'); + + if (totalQuantity === 0) { + AppToaster.show({ + message: intl.get('quantity_cannot_be_zero_or_empty'), + intent: Intent.DANGER, + }); + setSubmitting(false); + return; + } + const form = { + ...transformFormValuesToRequest(values), + }; + // Handle the request success. + const onSuccess = (response) => { + AppToaster.show({ + message: intl.get( + isNewMode + ? 'vendor_credits.success_message' + : 'vendor_credits.edit_success_message', + ), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + + if (submitPayload.redirect) { + history.push('/vendor-credits'); + } + if (submitPayload.resetForm) { + resetForm(); + } + }; + // Handle the request error. + const onError = ({ + response: { + data: { errors }, + }, + }) => { + setSubmitting(false); + }; + if (isNewMode) { + createVendorCreditMutate(form).then(onSuccess).catch(onError); + } else { + editVendorCreditMutate([vendorCredit.id, form]) + .then(onSuccess) + .catch(onError); + } + }; + + return ( +
+ + + + + + + + + +
+ ); +} + +export default compose( + withSettings(({ vendorsCreditNoteSetting }) => ({ + vendorcreditAutoIncrement: vendorsCreditNoteSetting?.autoIncrement, + vendorcreditNextNumber: vendorsCreditNoteSetting?.nextNumber, + vendorcreditNumberPrefix: vendorsCreditNoteSetting?.numberPrefix, + })), + withCurrentOrganization(), +)(VendorCreditNoteForm); diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js new file mode 100644 index 000000000..d3c5cabe4 --- /dev/null +++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js @@ -0,0 +1,41 @@ +import * as Yup from 'yup'; +import intl from 'react-intl-universal'; +import { DATATYPES_LENGTH } from 'common/dataTypes'; +import { isBlank } from 'utils'; + +const getSchema = Yup.object().shape({ + vendor_id: Yup.number().required().label(intl.get('vendor_name_')), + vendor_credit_date: Yup.date().required().label(intl.get('bill_date_')), + vendor_credit_number: Yup.string() + .max(DATATYPES_LENGTH.STRING) + .label(intl.get('bill_number_')), + reference_no: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING), + note: Yup.string() + .trim() + .min(1) + .max(DATATYPES_LENGTH.TEXT) + .label(intl.get('note')), + entries: Yup.array().of( + Yup.object().shape({ + quantity: Yup.number() + .nullable() + .max(DATATYPES_LENGTH.INT_10) + .when(['rate'], { + is: (rate) => rate, + then: Yup.number().required(), + }), + rate: Yup.number().nullable().max(DATATYPES_LENGTH.INT_10), + item_id: Yup.number() + .nullable() + .when(['quantity', 'rate'], { + is: (quantity, rate) => !isBlank(quantity) && !isBlank(rate), + then: Yup.number().required(), + }), + discount: Yup.number().nullable().min(0).max(DATATYPES_LENGTH.INT_10), + description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), + }), + ), +}); + +export const CreateCreditNoteFormSchema = getSchema; +export const EditCreditNoteFormSchema = getSchema; diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js new file mode 100644 index 000000000..5d5d69df2 --- /dev/null +++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js @@ -0,0 +1,27 @@ +import React from 'react'; +import VendorCreditNumberDialog from '../../../Dialogs/VendorCreditNumberDialog'; +import { useFormikContext } from 'formik'; + +/** + * Vendor credit form dialog. + */ +export default function VendorCreditNoteFormDialogs() { + // Update the form once the vendor credit number form submit confirm. + const handleVendorCreditNumberFormConfirm = ({ + incrementNumber, + manually, + }) => { + setFieldValue('vendor_credit_number', incrementNumber || ''); + setFieldValue('vendor_credit_no_manually', manually); + }; + const { setFieldValue } = useFormikContext(); + + return ( + + + + ); +} diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js new file mode 100644 index 000000000..ea4ee2d99 --- /dev/null +++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { FastField } from 'formik'; +import { FormGroup, TextArea } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'components'; +import { CLASSES } from 'common/classes'; +import { Row, Col, Postbox } from 'components'; +import { inputIntent } from 'utils'; +import classNames from 'classnames'; + +/** + * Vendor Credit note form footer. + */ +export default function VendorCreditNoteFormFooter() { + return ( +
+ } + defaultOpen={false} + > + + + + {({ field, meta: { error, touched } }) => ( + } + className={'form-group--note'} + intent={inputIntent({ error, touched })} + > +