diff --git a/client/src/common/contactsOptions.js b/client/src/common/contactsOptions.js new file mode 100644 index 000000000..0cbe1a5ed --- /dev/null +++ b/client/src/common/contactsOptions.js @@ -0,0 +1,4 @@ +export default [ + { name: 'Customer', path: 'customers' }, + { name: 'Vendor', path: 'vendors' }, +]; diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index 7a3bdbea4..12e26077c 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -9,7 +9,7 @@ import ExchangeRateFormDialog from 'containers/Dialogs/ExchangeRateFormDialog'; import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFormDialog'; import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog'; import KeyboardShortcutsDialog from 'containers/Dialogs/keyboardShortcutsDialog'; - +import ContactDuplicateDialog from 'containers/Dialogs/ContactDuplicateDialog'; /** * Dialogs container. */ @@ -24,6 +24,7 @@ export default function DialogsContainer() { + ); } diff --git a/client/src/containers/Accounting/JournalsLanding/utils.js b/client/src/containers/Accounting/JournalsLanding/utils.js index 9cd8be409..bedabbb83 100644 --- a/client/src/containers/Accounting/JournalsLanding/utils.js +++ b/client/src/containers/Accounting/JournalsLanding/utils.js @@ -44,17 +44,17 @@ export const useManualJournalsColumns = () => { className: 'journal_type', }, { - id: 'publish', + id: 'status', Header: formatMessage({ id: 'publish' }), accessor: (row) => StatusAccessor(row), width: 95, - className: 'publish', + className: 'status', }, { id: 'note', Header: formatMessage({ id: 'note' }), accessor: NoteAccessor, - disableSorting: true, + disableSortBy: true, width: 85, className: 'note', }, diff --git a/client/src/containers/Alerts/Items/ItemDeleteAlert.js b/client/src/containers/Alerts/Items/ItemDeleteAlert.js index 93e6a2a96..553868b04 100644 --- a/client/src/containers/Alerts/Items/ItemDeleteAlert.js +++ b/client/src/containers/Alerts/Items/ItemDeleteAlert.js @@ -30,7 +30,7 @@ function ItemDeleteAlert({ closeAlert, // #withItemsActions - addItemsTableQueries + addItemsTableQueries, }) { const { mutateAsync: deleteItem, isLoading } = useDeleteItem(); const { formatMessage } = useIntl(); @@ -53,9 +53,15 @@ function ItemDeleteAlert({ // Reset to page number one. addItemsTableQueries({ page: 1 }); }) - .catch(({ errors }) => { - handleDeleteErrors(errors); - }) + .catch( + ({ + response: { + data: { errors }, + }, + }) => { + handleDeleteErrors(errors); + }, + ) .finally(() => { closeAlert(name); }); @@ -84,5 +90,5 @@ function ItemDeleteAlert({ export default compose( withAlertStoreConnect(), withAlertActions, - withItemsActions + withItemsActions, )(ItemDeleteAlert); diff --git a/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js b/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js index 017c4e5b0..d7b1abb0c 100644 --- a/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js +++ b/client/src/containers/Customers/CustomerForm/CustomerFloatingActions.js @@ -25,7 +25,7 @@ export default function CustomerFloatingActions() { const history = useHistory(); // Customer form context. - const { customerId, setSubmitPayload } = useCustomerFormContext(); + const { isNewMode,setSubmitPayload } = useCustomerFormContext(); // Formik context. const { resetForm, submitForm, isSubmitting } = useFormikContext(); @@ -61,7 +61,7 @@ export default function CustomerFloatingActions() { intent={Intent.PRIMARY} type="submit" onClick={handleSubmitBtnClick} - text={customerId ? : } + text={!isNewMode ? : } /> : } + text={!isNewMode ? : } /> {/* ----------- Cancel ----------- */} + + + + + + )} + + ); +} + +export default compose(withDialogActions)(ContactDuplicateForm); diff --git a/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateProvider.js b/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateProvider.js new file mode 100644 index 000000000..a6a66c1ac --- /dev/null +++ b/client/src/containers/Dialogs/ContactDuplicateDialog/ContactDuplicateProvider.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { DialogContent } from 'components'; + +const ContactDuplicateContext = React.createContext(); + +/** + * contact duplicate provider. + */ +function ContactDuplicateProvider({ contactId, dialogName, ...props }) { + // Provider state. + const provider = { + dialogName, + contactId, + }; + + return ( + + + + ); +} + +const useContactDuplicateFromContext = () => + React.useContext(ContactDuplicateContext); + +export { ContactDuplicateProvider, useContactDuplicateFromContext }; diff --git a/client/src/containers/Dialogs/ContactDuplicateDialog/index.js b/client/src/containers/Dialogs/ContactDuplicateDialog/index.js new file mode 100644 index 000000000..af68c318e --- /dev/null +++ b/client/src/containers/Dialogs/ContactDuplicateDialog/index.js @@ -0,0 +1,33 @@ +import React, { lazy } from 'react'; +import { FormattedMessage as T } from 'react-intl'; +import { Dialog, DialogSuspense } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose } from 'utils'; + +const ContactDialogContent = lazy(() => + import('./ContactDuplicateDialogContent'), +); +/** + * Contact duplicate dialog. + */ +function ContactDuplicateDialog({ dialogName, payload, isOpen }) { + return ( + } + autoFocus={true} + canEscapeKeyClose={true} + className={'dialog--contact-duplicate'} + isOpen={isOpen} + > + + + + + ); +} + +export default compose(withDialogRedux())(ContactDuplicateDialog); diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js index 88e260f38..5a6964a2d 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js @@ -3,9 +3,6 @@ import React from 'react'; import ExchangeRateForm from './ExchangeRateForm'; import { ExchangeRateFormProvider } from './ExchangeRateFormProvider'; -import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail'; -import { compose } from 'utils'; - import 'style/pages/ExchangeRate/ExchangeRateDialog.scss'; /** diff --git a/client/src/containers/Expenses/ExpensesLanding/components.js b/client/src/containers/Expenses/ExpensesLanding/components.js index da7e98761..2b4d1c89d 100644 --- a/client/src/containers/Expenses/ExpensesLanding/components.js +++ b/client/src/containers/Expenses/ExpensesLanding/components.js @@ -139,28 +139,28 @@ export function useExpensesTableColumns() { className: 'payment_date', }, { - id: 'total_amount', + id: 'amount', Header: formatMessage({ id: 'full_amount' }), accessor: TotalAmountAccessor, - className: 'total_amount', + className: 'amount', width: 150, }, { - id: 'payment_account_id', + id: 'payment_account', Header: formatMessage({ id: 'payment_account' }), accessor: 'payment_account.name', className: 'payment_account', width: 150, }, { - id: 'expense_account_id', + id: 'expense_account', Header: formatMessage({ id: 'expense_account' }), accessor: ExpenseAccountAccessor, width: 160, className: 'expense_account', }, { - id: 'publish', + id: 'published', Header: formatMessage({ id: 'publish' }), accessor: PublishAccessor, width: 100, diff --git a/client/src/containers/InventoryAdjustments/components.js b/client/src/containers/InventoryAdjustments/components.js index 0f0bbc3e5..d9a975103 100644 --- a/client/src/containers/InventoryAdjustments/components.js +++ b/client/src/containers/InventoryAdjustments/components.js @@ -158,7 +158,7 @@ export const useInventoryAdjustmentsColumns = () => { width: 100, }, { - id: 'publish', + id: 'published_at', Header: formatMessage({ id: 'status' }), accessor: PublishAccessor, width: 95, diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index 78adb5345..16decaf0f 100644 --- a/client/src/containers/Items/ItemForm.js +++ b/client/src/containers/Items/ItemForm.js @@ -60,14 +60,14 @@ function ItemForm({ createItemMutate, editItemMutate, submitPayload, - isNewMode + isNewMode, } = useItemFormContext(); // History context. const history = useHistory(); const { formatMessage } = useIntl(); - + /** * Initial values in create and edit mode. */ @@ -97,7 +97,11 @@ function ItemForm({ // Transform API errors. const transformApiErrors = (error) => { - const { response: { data: { errors } } } = error; + const { + response: { + data: { errors }, + }, + } = error; const fields = {}; if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) { @@ -118,9 +122,10 @@ function ItemForm({ AppToaster.show({ message: formatMessage( { - id: isNewMode - ? 'the_item_has_been_created_successfully' - : 'the_item_has_been_edited_successfully', + id: + isNewMode + ? 'the_item_has_been_created_successfully' + : 'the_item_has_been_edited_successfully', }, { number: itemId, @@ -151,7 +156,7 @@ function ItemForm({ editItemMutate([itemId, form]).then(onSuccess).catch(onError); } }; - + return (
{ - !isNewMode - ? changePageTitle(formatMessage({ id: 'edit_item_details' })) - : changePageTitle(formatMessage({ id: 'new_item' })); + isNewMode + ? changePageTitle(formatMessage({ id: 'new_item' })) + : changePageTitle(formatMessage({ id: 'edit_item_details' })); }, [changePageTitle, isNewMode, formatMessage]); return ( diff --git a/client/src/containers/Items/ItemsDataTable.js b/client/src/containers/Items/ItemsDataTable.js index df1a02dc3..d60463eb5 100644 --- a/client/src/containers/Items/ItemsDataTable.js +++ b/client/src/containers/Items/ItemsDataTable.js @@ -7,7 +7,6 @@ import ItemsEmptyStatus from './ItemsEmptyStatus'; import TableSkeletonRows from 'components/Datatable/TableSkeletonRows'; import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton'; - import withItems from 'containers/Items/withItems'; import withItemsActions from 'containers/Items/withItemsActions'; import withAlertsActions from 'containers/Alert/withAlertActions'; @@ -94,6 +93,11 @@ function ItemsDataTable({ }; // Display empty status instead of the table. + const handleDuplicate = ({ id }) => { + history.push(`/items/new?duplicate=${id}`, { action: id }); + }; + + // Cannot continue in case the items has empty status. if (isEmptyStatus) { return ; } @@ -103,29 +107,23 @@ function ItemsDataTable({ columns={columns} data={items} initialState={itemsTableState} - loading={isItemsLoading} headerLoading={isItemsLoading} progressBarLoading={isItemsFetching} - noInitialFetch={true} selectionColumn={true} spinnerProps={{ size: 30 }} expandable={false} sticky={true} rowClassNames={rowClassNames} - pagination={true} manualSortBy={true} manualPagination={true} pagesCount={pagination.pagesCount} - autoResetSortBy={false} autoResetPage={true} - TableLoadingRenderer={TableSkeletonRows} TableHeaderSkeletonRenderer={TableSkeletonHeader} - ContextMenu={ItemsActionMenuList} onFetchData={handleFetchData} payload={{ @@ -134,6 +132,7 @@ function ItemsDataTable({ onInactivateItem: handleInactiveItem, onActivateItem: handleActivateItem, onMakeAdjustment: handleMakeAdjustment, + onDuplicate: handleDuplicate, }} noResults={'There is no items in the table yet.'} {...tableProps} diff --git a/client/src/containers/Items/components.js b/client/src/containers/Items/components.js index a2408281a..24af7fb21 100644 --- a/client/src/containers/Items/components.js +++ b/client/src/containers/Items/components.js @@ -79,10 +79,10 @@ export function ItemsActionMenuList({ onActivateItem, onMakeAdjustment, onDeleteItem, + onDuplicate, }, }) { const { formatMessage } = useIntl(); - return ( + } + text={formatMessage({ id: 'duplicate' })} + onClick={safeCallback(onDuplicate, original)} + /> ); -}; +} export const ItemsActionsTableCell = (props) => { return ( @@ -137,7 +142,6 @@ export const ItemsActionsTableCell = (props) => { ); }; - /** * Retrieve all items table columns. */ @@ -147,30 +151,35 @@ export const useItemsTableColumns = () => { return React.useMemo( () => [ { + id: 'name', Header: formatMessage({ id: 'item_name' }), accessor: 'name', className: 'name', width: 180, }, { + id: 'code', Header: formatMessage({ id: 'item_code' }), accessor: 'code', className: 'code', width: 120, }, { + id: 'type', Header: formatMessage({ id: 'item_type' }), accessor: ItemTypeAccessor, className: 'item_type', width: 120, }, { + id: 'category', Header: formatMessage({ id: 'category' }), accessor: 'category.name', className: 'category', width: 150, }, { + id: 'sell_price', Header: formatMessage({ id: 'sell_price' }), Cell: SellPriceCell, accessor: 'sell_price', @@ -178,6 +187,7 @@ export const useItemsTableColumns = () => { width: 150, }, { + id: 'cost_price', Header: formatMessage({ id: 'cost_price' }), Cell: CostPriceCell, accessor: 'cost_price', @@ -185,6 +195,7 @@ export const useItemsTableColumns = () => { width: 150, }, { + id: 'quantity_on_hand', Header: formatMessage({ id: 'quantity_on_hand' }), accessor: 'quantity_on_hand', Cell: QuantityOnHandCell, @@ -199,4 +210,4 @@ export const useItemsTableColumns = () => { ], [formatMessage], ); -} \ No newline at end of file +}; diff --git a/client/src/containers/Purchases/Bills/BillsLanding/components.js b/client/src/containers/Purchases/Bills/BillsLanding/components.js index bc8abd309..51d04bc78 100644 --- a/client/src/containers/Purchases/Bills/BillsLanding/components.js +++ b/client/src/containers/Purchases/Bills/BillsLanding/components.js @@ -112,11 +112,11 @@ export function useBillsTableColumns() { className: 'bill_date', }, { - id: 'vendor_id', + id: 'vendor', Header: formatMessage({ id: 'vendor_name' }), accessor: 'vendor.display_name', width: 140, - className: 'vendor_id', + className: 'vendor', }, { id: 'bill_number', diff --git a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js index ade85b6a7..6db431183 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentsLanding/components.js @@ -83,7 +83,7 @@ export function usePaymentMadesTableColumns() { className: 'payment_date', }, { - id: 'vendor_id', + id: 'vendor', Header: formatMessage({ id: 'vendor_name' }), accessor: 'vendor.display_name', width: 140, @@ -98,7 +98,7 @@ export function usePaymentMadesTableColumns() { className: 'payment_number', }, { - id: 'payment_account_id', + id: 'payment_account', Header: formatMessage({ id: 'payment_account' }), accessor: 'payment_account.name', width: 140, @@ -112,7 +112,7 @@ export function usePaymentMadesTableColumns() { className: 'amount', }, { - id: 'reference', + id: 'reference_no', Header: formatMessage({ id: 'reference' }), accessor: 'reference', width: 140, diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js index afb0870c3..c11f16b50 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js @@ -153,7 +153,7 @@ export function useEstiamtesTableColumns() { className: 'estimate_date', }, { - id: 'customer_id', + id: 'customer', Header: formatMessage({ id: 'customer_name' }), accessor: 'customer.display_name', width: 140, @@ -190,11 +190,11 @@ export function useEstiamtesTableColumns() { className: 'status', }, { - id: 'reference', + id: 'reference_no', Header: formatMessage({ id: 'reference_no' }), - accessor: 'reference', - width: 140, - className: 'reference', + accessor: 'reference_no', + width: 90, + className: 'reference_no', }, { id: 'actions', diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js index d8311edde..1d1499822 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js @@ -159,7 +159,7 @@ export function useInvoicesTableColumns() { className: 'invoice_date', }, { - id: 'customer_id', + id: 'customer', Header: formatMessage({ id: 'customer_name' }), accessor: 'customer.display_name', width: 180, diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js index e98d3af31..38878355f 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js @@ -93,7 +93,7 @@ export function usePaymentReceivesColumns() { className: 'payment_date', }, { - id: 'customer_id', + id: 'customer', Header: formatMessage({ id: 'customer_name' }), accessor: 'customer.display_name', width: 160, @@ -122,7 +122,7 @@ export function usePaymentReceivesColumns() { className: 'reference_no', }, { - id: 'deposit_account_id', + id: 'deposit_account', Header: formatMessage({ id: 'deposit_account' }), accessor: 'deposit_account.name', width: 140, diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js index ed8cbc3d5..6e547f4f1 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/components.js @@ -104,7 +104,7 @@ export function useReceiptsTableColumns() { className: 'receipt_date', }, { - id: 'customer_id', + id: 'customer', Header: formatMessage({ id: 'customer_name' }), accessor: 'customer.display_name', width: 140, @@ -119,7 +119,7 @@ export function useReceiptsTableColumns() { className: 'receipt_number', }, { - id: 'deposit_account_id', + id: 'deposit_account', Header: formatMessage({ id: 'deposit_account' }), accessor: 'deposit_account.name', width: 140, diff --git a/client/src/containers/Vendors/VendorForm/VendorForm.js b/client/src/containers/Vendors/VendorForm/VendorForm.js index db2b916de..eeaf20a2b 100644 --- a/client/src/containers/Vendors/VendorForm/VendorForm.js +++ b/client/src/containers/Vendors/VendorForm/VendorForm.js @@ -69,11 +69,11 @@ function VendorForm({ // #withSettings baseCurrency, }) { - // Vendor form context. const { vendorId, vendor, + contactDuplicate, createVendorMutate, editVendorMutate, setSubmitPayload, @@ -94,9 +94,9 @@ function VendorForm({ () => ({ ...defaultInitialValues, currency_code: baseCurrency, - ...transformToForm(vendor, defaultInitialValues), + ...transformToForm(vendor || contactDuplicate, defaultInitialValues), }), - [vendor, baseCurrency], + [vendor, contactDuplicate, baseCurrency], ); useEffect(() => { diff --git a/client/src/containers/Vendors/VendorForm/VendorFormProvider.js b/client/src/containers/Vendors/VendorForm/VendorFormProvider.js index e559846b9..cd3052e85 100644 --- a/client/src/containers/Vendors/VendorForm/VendorFormProvider.js +++ b/client/src/containers/Vendors/VendorForm/VendorFormProvider.js @@ -1,8 +1,12 @@ import React, { useState, createContext } from 'react'; +import { omit, pick } from 'lodash'; +import { useLocation } from 'react-router-dom'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { useVendor, + useContact, useCurrencies, + useCustomer, useCreateVendor, useEditVendor, } from 'hooks/query'; @@ -13,6 +17,8 @@ const VendorFormContext = createContext(); * Vendor form provider. */ function VendorFormProvider({ vendorId, ...props }) { + const { state } = useLocation(); + // Handle fetch Currencies data table const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies(); @@ -21,6 +27,16 @@ function VendorFormProvider({ vendorId, ...props }) { enabled: !!vendorId, }); + const contactId = state?.action; + + // Handle fetch contact duplicate details. + const { data: contactDuplicate, isFetching: isContactLoading } = useContact( + contactId, + { + enabled: !!contactId, + }, + ); + // Create and edit vendor mutations. const { mutateAsync: createVendorMutate } = useCreateVendor(); const { mutateAsync: editVendorMutate } = useEditVendor(); @@ -32,6 +48,7 @@ function VendorFormProvider({ vendorId, ...props }) { vendorId, currencies, vendor, + contactDuplicate: { ...omit(contactDuplicate, ['opening_balance_at']) }, submitPayload, createVendorMutate, @@ -41,7 +58,7 @@ function VendorFormProvider({ vendorId, ...props }) { return ( diff --git a/client/src/containers/Vendors/VendorsLanding/VendorsTable.js b/client/src/containers/Vendors/VendorsLanding/VendorsTable.js index d02d7bdbf..18efd3718 100644 --- a/client/src/containers/Vendors/VendorsLanding/VendorsTable.js +++ b/client/src/containers/Vendors/VendorsLanding/VendorsTable.js @@ -11,6 +11,7 @@ import { useVendorsListContext } from './VendorsListProvider'; import withVendorsActions from './withVendorsActions'; import withVendors from './withVendors'; import withAlertsActions from 'containers/Alert/withAlertActions'; +import withDialogActions from 'containers/Dialog/withDialogActions'; import { compose } from 'utils'; import { ActionsMenu, useVendorsTableColumns } from './components'; @@ -27,6 +28,8 @@ function VendorsTable({ // #withAlertsActions openAlert, + // #withDialogActions + openDialog, }) { // Vendors list context. const { @@ -34,7 +37,7 @@ function VendorsTable({ pagination, isVendorsFetching, isVendorsLoading, - isEmptyStatus + isEmptyStatus, } = useVendorsListContext(); // Vendors table columns. @@ -53,6 +56,12 @@ function VendorsTable({ openAlert('vendor-delete', { vendorId: id }); }; + // Handle contact duplicate . + const handleContactDuplicate = ({ id }) => { + openDialog('contact-duplicate', { + contactId: id, + }); + }; // Handle fetch data once the page index, size or sort by of the table change. const handleFetchData = React.useCallback( ({ pageSize, pageIndex, sortBy }) => { @@ -67,7 +76,7 @@ function VendorsTable({ // Display empty status instead of the table. if (isEmptyStatus) { - return + return ; } return ( @@ -94,6 +103,7 @@ function VendorsTable({ payload={{ onEdit: handleEditVendor, onDelete: handleDeleteVendor, + onDuplicate: handleContactDuplicate, }} /> ); @@ -102,5 +112,6 @@ function VendorsTable({ export default compose( withVendorsActions, withAlertsActions, + withDialogActions, withVendors(({ vendorsTableState }) => ({ vendorsTableState })), )(VendorsTable); diff --git a/client/src/containers/Vendors/VendorsLanding/components.js b/client/src/containers/Vendors/VendorsLanding/components.js index 66c651aa6..a737ba63c 100644 --- a/client/src/containers/Vendors/VendorsLanding/components.js +++ b/client/src/containers/Vendors/VendorsLanding/components.js @@ -17,7 +17,7 @@ import { safeCallback, firstLettersArgs } from 'utils'; */ export function ActionsMenu({ row: { original }, - payload: { onEdit, onDelete }, + payload: { onEdit, onDelete, onDuplicate }, }) { const { formatMessage } = useIntl(); @@ -33,6 +33,11 @@ export function ActionsMenu({ text={formatMessage({ id: 'edit_vendor' })} onClick={safeCallback(onEdit, original)} /> + } + text={formatMessage({ id: 'duplicate' })} + onClick={safeCallback(onDuplicate, original)} + /> } text={formatMessage({ id: 'delete_vendor' })} diff --git a/client/src/hooks/query/contacts.js b/client/src/hooks/query/contacts.js new file mode 100644 index 000000000..16b0d44c1 --- /dev/null +++ b/client/src/hooks/query/contacts.js @@ -0,0 +1,14 @@ +import { useQuery } from 'react-query'; +import useApiRequest from '../useRequest'; + +/** + * Retrieve the contact duplicate. + */ +export function useContact(id, props) { + const apiRequest = useApiRequest(); + + return useQuery(['CONTACT', id], () => apiRequest.get(`contacts/${id}`), { + select: (res) => res.data.customer, + ...props, + }); +} diff --git a/client/src/hooks/query/index.js b/client/src/hooks/query/index.js index 02dd868a5..268856ea1 100644 --- a/client/src/hooks/query/index.js +++ b/client/src/hooks/query/index.js @@ -20,3 +20,4 @@ export * from './settings'; export * from './users'; export * from './invite'; export * from './exchangeRates'; +export * from './contacts'; diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 8dc555168..bdcfb955a 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -990,4 +990,10 @@ export default { convert_to_invoice: 'Convert to Invoice', sale_estimate_is_already_converted_to_invoice: 'Sale estimate is already converted to invoice.', + duplicate: 'Duplicate', + are_you_sure_want_to_duplicate: + 'Are you sure want to duplicate this contact, which contact type?', + contact_type: 'Contact Type', + duplicate_contact: 'Duplicate Contact', + contact_type_: 'Contact type', }; diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 7ea5bb2aa..9aaf1614b 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -4,7 +4,6 @@ import { formatMessage } from 'services/intl'; // const BASE_URL = '/dashboard'; export default [ - // Accounts. { path: `/accounts`, @@ -72,6 +71,13 @@ export default [ pageTitle: formatMessage({ id: 'edit_item' }), backLink: true, }, + { + path: `/items/new?duplicate=/:id`, + component: lazy({ + loader: () => import('containers/Items/ItemFormPage'), + }), + breadcrumb: 'Duplicate Item', + }, { path: `/items/new`, component: lazy(() => import('containers/Items/ItemFormPage')), @@ -132,7 +138,7 @@ export default [ hotkey: 'shift+5', pageTitle: formatMessage({ id: 'trial_balance_sheet' }), backLink: true, - sidebarShrink: true + sidebarShrink: true, }, { path: `/financial-reports/profit-loss-sheet`, @@ -254,6 +260,16 @@ export default [ hotkey: 'shift+c', pageTitle: formatMessage({ id: 'customers_list' }), }, + { + path: `/customers/contact_duplicate=/:id`, + component: lazy(() => + import('containers/Customers/CustomerForm/CustomerFormPage'), + ), + name: 'duplicate-customer', + breadcrumb: 'Duplicate Customer', + pageTitle: formatMessage({ id: 'new_customer' }), + backLink: true, + }, // Vendors { @@ -286,6 +302,16 @@ export default [ hotkey: 'shift+v', pageTitle: formatMessage({ id: 'vendors_list' }), }, + { + path: `/vendors/contact_duplicate=/:id`, + component: lazy(() => + import('containers/Vendors/VendorForm/VendorFormPage'), + ), + name: 'duplicate-vendor', + breadcrumb: 'Duplicate Vendor', + pageTitle: formatMessage({ id: 'new_vendor' }), + backLink: true, + }, // Estimates { diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js index 86a0d3ed4..7ca4d037a 100644 --- a/client/src/static/json/icons.js +++ b/client/src/static/json/icons.js @@ -406,4 +406,10 @@ export default { ], viewBox: '0 0 24 24', }, + 'duplicate-18': { + path: [ + 'M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z', + ], + viewBox: '0 0 24 24', + }, }; diff --git a/client/src/style/pages/ContactDuplicate/ContactDuplicateDialog.scss b/client/src/style/pages/ContactDuplicate/ContactDuplicateDialog.scss new file mode 100644 index 000000000..e3f297398 --- /dev/null +++ b/client/src/style/pages/ContactDuplicate/ContactDuplicateDialog.scss @@ -0,0 +1,20 @@ +.dialog--contact-duplicate { + .bp3-dialog-body { + .bp3-form-group.bp3-inline { + margin: 18px 0px; + .bp3-label { + min-width: 100px; + } + } + .bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) { + width: 260px; + } + } + + .bp3-dialog-footer { + .bp3-dialog-footer-actions .bp3-button { + margin-left: 8px; + min-width: 65px; + } + } +} diff --git a/server/src/models/BillPayment.js b/server/src/models/BillPayment.js index 5b823f219..52ddd96d0 100644 --- a/server/src/models/BillPayment.js +++ b/server/src/models/BillPayment.js @@ -1,19 +1,19 @@ -import { Model } from 'objection'; -import TenantModel from 'models/TenantModel'; +import { Model } from "objection"; +import TenantModel from "models/TenantModel"; export default class BillPayment extends TenantModel { /** * Table name */ static get tableName() { - return 'bills_payments'; + return "bills_payments"; } /** * Timestamps columns. */ get timestamps() { - return ['createdAt', 'updatedAt']; + return ["createdAt", "updatedAt"]; } static get resourceable() { @@ -24,18 +24,18 @@ export default class BillPayment extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const BillPaymentEntry = require('models/BillPaymentEntry'); - const AccountTransaction = require('models/AccountTransaction'); - const Contact = require('models/Contact'); - const Account = require('models/Account'); + const BillPaymentEntry = require("models/BillPaymentEntry"); + const AccountTransaction = require("models/AccountTransaction"); + const Contact = require("models/Contact"); + const Account = require("models/Account"); return { entries: { relation: Model.HasManyRelation, modelClass: BillPaymentEntry.default, join: { - from: 'bills_payments.id', - to: 'bills_payments_entries.billPaymentId', + from: "bills_payments.id", + to: "bills_payments_entries.billPaymentId", }, }, @@ -43,20 +43,20 @@ export default class BillPayment extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: Contact.default, join: { - from: 'bills_payments.vendorId', - to: 'contacts.id', + from: "bills_payments.vendorId", + to: "contacts.id", }, filter(query) { - query.where('contact_service', 'vendor'); - } + query.where("contact_service", "vendor"); + }, }, paymentAccount: { relation: Model.BelongsToOneRelation, modelClass: Account.default, join: { - from: 'bills_payments.paymentAccountId', - to: 'accounts.id', + from: "bills_payments.paymentAccountId", + to: "accounts.id", }, }, @@ -64,11 +64,11 @@ export default class BillPayment extends TenantModel { relation: Model.HasManyRelation, modelClass: AccountTransaction.default, join: { - from: 'bills_payments.id', - to: 'accounts_transactions.referenceId' + from: "bills_payments.id", + to: "accounts_transactions.referenceId", }, filter(builder) { - builder.where('reference_type', 'BillPayment'); + builder.where("reference_type", "BillPayment"); }, }, }; @@ -80,16 +80,22 @@ export default class BillPayment extends TenantModel { static get fields() { return { vendor: { - lable: "Vendor name", - column: 'vendor_id', - relation: 'contacts.id', - relationColumn: 'contacts.display_name', + label: "Vendor name", + column: "vendor_id", + relation: "contacts.id", + relationColumn: "contacts.display_name", }, amount: { label: "Amount", column: "amount", - columnType: 'number', - fieldType: 'number', + columnType: "number", + fieldType: "number", + }, + due_amount: { + label: "Due amount", + column: "due_amount", + columnType: "number", + fieldType: "number", }, payment_account: { label: "Payment account", @@ -97,40 +103,40 @@ export default class BillPayment extends TenantModel { relation: "accounts.id", relationColumn: "accounts.name", - fieldType: 'options', - optionsResource: 'Account', - optionsKey: 'id', - optionsLabel: 'name', + fieldType: "options", + optionsResource: "Account", + optionsKey: "id", + optionsLabel: "name", }, payment_number: { label: "Payment number", column: "payment_number", - columnType: 'string', - fieldType: 'text', + columnType: "string", + fieldType: "text", }, payment_date: { label: "Payment date", column: "payment_date", - columnType: 'date', - fieldType: 'date', + columnType: "date", + fieldType: "date", }, reference_no: { label: "Reference No.", column: "reference", - columnType: 'string', - fieldType: 'text', + columnType: "string", + fieldType: "text", }, description: { label: "Description", column: "description", - columnType: 'string', - fieldType: 'text', + columnType: "string", + fieldType: "text", }, created_at: { - label: 'Created at', - column: 'created_at', - columnType: 'date', + label: "Created at", + column: "created_at", + columnType: "date", }, - } + }; } } diff --git a/server/src/models/Item.js b/server/src/models/Item.js index 83e622ea8..c2a7b9da1 100644 --- a/server/src/models/Item.js +++ b/server/src/models/Item.js @@ -1,22 +1,20 @@ -import { Model } from 'objection'; -import TenantModel from 'models/TenantModel'; -import { - buildFilterQuery, -} from 'lib/ViewRolesBuilder'; +import { Model } from "objection"; +import TenantModel from "models/TenantModel"; +import { buildFilterQuery } from "lib/ViewRolesBuilder"; export default class Item extends TenantModel { /** * Table name */ static get tableName() { - return 'items'; + return "items"; } /** * Model timestamps. */ get timestamps() { - return ['createdAt', 'updatedAt']; + return ["createdAt", "updatedAt"]; } /** @@ -44,9 +42,9 @@ export default class Item extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Media = require('models/Media'); - const Account = require('models/Account'); - const ItemCategory = require('models/ItemCategory'); + const Media = require("models/Media"); + const Account = require("models/Account"); + const ItemCategory = require("models/ItemCategory"); return { /** @@ -56,8 +54,8 @@ export default class Item extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: ItemCategory.default, join: { - from: 'items.categoryId', - to: 'items_categories.id', + from: "items.categoryId", + to: "items_categories.id", }, }, @@ -65,8 +63,8 @@ export default class Item extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: Account.default, join: { - from: 'items.costAccountId', - to: 'accounts.id', + from: "items.costAccountId", + to: "accounts.id", }, }, @@ -74,8 +72,8 @@ export default class Item extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: Account.default, join: { - from: 'items.sellAccountId', - to: 'accounts.id', + from: "items.sellAccountId", + to: "accounts.id", }, }, @@ -83,8 +81,8 @@ export default class Item extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: Account.default, join: { - from: 'items.inventoryAccountId', - to: 'accounts.id', + from: "items.inventoryAccountId", + to: "accounts.id", }, }, @@ -92,13 +90,13 @@ export default class Item extends TenantModel { relation: Model.ManyToManyRelation, modelClass: Media.default, join: { - from: 'items.id', + from: "items.id", through: { - from: 'media_links.model_id', - to: 'media_links.media_id', + from: "media_links.model_id", + to: "media_links.media_id", }, - to: 'media.id', - } + to: "media.id", + }, }, }; } @@ -109,72 +107,76 @@ export default class Item extends TenantModel { static get fields() { return { type: { - label: 'Type', - column: 'type', + label: "Type", + column: "type", }, name: { - label: 'Name', - column: 'name', + label: "Name", + column: "name", + }, + code: { + label: "Code", + column: "code", }, sellable: { - label: 'Sellable', - column: 'sellable', + label: "Sellable", + column: "sellable", }, purchasable: { - label: 'Purchasable', - column: 'purchasable', + label: "Purchasable", + column: "purchasable", }, sell_price: { - label: 'Sell price', - column: 'sell_price' + label: "Sell price", + column: "sell_price", }, cost_price: { - label: 'Cost price', - column: 'cost_price', + label: "Cost price", + column: "cost_price", }, currency_code: { - label: 'Currency', - column: 'currency_code', + label: "Currency", + column: "currency_code", }, cost_account: { - label: 'Cost account', - column: 'cost_account_id', - relation: 'accounts.id', - relationColumn: 'accounts.name', + label: "Cost account", + column: "cost_account_id", + relation: "accounts.id", + relationColumn: "accounts.name", }, sell_account: { - label: 'Sell account', - column: 'sell_account_id', - relation: 'accounts.id', - relationColumn: 'accounts.name', + label: "Sell account", + column: "sell_account_id", + relation: "accounts.id", + relationColumn: "accounts.name", }, inventory_account: { label: "Inventory account", - column: 'inventory_account_id', - relation: 'accounts.id', - relationColumn: 'accounts.name', + column: "inventory_account_id", + relation: "accounts.id", + relationColumn: "accounts.name", }, sell_description: { label: "Sell description", - column: 'sell_description', + column: "sell_description", }, purchase_description: { label: "Purchase description", - column: 'purchase_description', + column: "purchase_description", }, quantity_on_hand: { label: "Quantity on hand", - column: 'quantity_on_hand', + column: "quantity_on_hand", }, note: { - label: 'Note', - column: 'note', + label: "Note", + column: "note", }, category: { label: "Category", - column: 'category_id', - relation: 'items_categories.id', - relationColumn: 'items_categories.name', + column: "category_id", + relation: "items_categories.id", + relationColumn: "items_categories.name", }, // user: { // label: 'User', @@ -183,10 +185,10 @@ export default class Item extends TenantModel { // relationColumn: 'users.', // }, created_at: { - label: 'Created at', - column: 'created_at', - columnType: 'date', - fieldType: 'date', + label: "Created at", + column: "created_at", + columnType: "date", + fieldType: "date", }, }; } diff --git a/server/src/models/SaleEstimate.js b/server/src/models/SaleEstimate.js index 20d6d5e20..4924a46f8 100644 --- a/server/src/models/SaleEstimate.js +++ b/server/src/models/SaleEstimate.js @@ -172,6 +172,12 @@ export default class SaleEstimate extends TenantModel { columnType: 'number', fieldType: 'number', }, + estimate_number: { + label: 'Estimate number', + column: 'estimate_number', + columnType: 'text', + fieldType: 'text', + }, customer: { label: 'Customer', column: 'customer_id', @@ -195,6 +201,12 @@ export default class SaleEstimate extends TenantModel { columnType: 'date', fieldType: 'date', }, + reference_no: { + label: "Reference No.", + column: "reference_no", + columnType: "number", + fieldType: "number", + }, note: { label: 'Note', column: 'note', diff --git a/server/src/models/SaleInvoice.js b/server/src/models/SaleInvoice.js index 71c89aaf4..c0dfe3bfb 100644 --- a/server/src/models/SaleInvoice.js +++ b/server/src/models/SaleInvoice.js @@ -1,6 +1,5 @@ import { Model, raw } from 'objection'; import moment from 'moment'; -import knex from 'knex'; import TenantModel from 'models/TenantModel'; export default class SaleInvoice extends TenantModel { @@ -8,14 +7,14 @@ export default class SaleInvoice extends TenantModel { * Table name */ static get tableName() { - return 'sales_invoices'; + return "sales_invoices"; } /** * Timestamps columns. */ get timestamps() { - return ['created_at', 'updated_at']; + return ["created_at", "updated_at"]; } /** @@ -23,14 +22,14 @@ export default class SaleInvoice extends TenantModel { */ static get virtualAttributes() { return [ - 'dueAmount', - 'isDelivered', - 'isOverdue', - 'isPartiallyPaid', - 'isFullyPaid', - 'isPaid', - 'remainingDays', - 'overdueDays', + "dueAmount", + "isDelivered", + "isOverdue", + "isPartiallyPaid", + "isFullyPaid", + "isPaid", + "remainingDays", + "overdueDays", ]; } @@ -96,7 +95,7 @@ export default class SaleInvoice extends TenantModel { const date = moment(); const dueDate = moment(this.dueDate); - return Math.max(dueDate.diff(date, 'days'), 0); + return Math.max(dueDate.diff(date, "days"), 0); } /** @@ -115,7 +114,7 @@ export default class SaleInvoice extends TenantModel { * * @param {*} asDate */ - getOverdueDays(asDate = moment().format('YYYY-MM-DD')) { + getOverdueDays(asDate = moment().format("YYYY-MM-DD")) { // Can't continue in case due date not defined. if (!this.dueDate) { return null; @@ -124,7 +123,7 @@ export default class SaleInvoice extends TenantModel { const date = moment(asDate); const dueDate = moment(this.dueDate); - return Math.max(date.diff(dueDate, 'days'), 0); + return Math.max(date.diff(dueDate, "days"), 0); } /** @@ -136,65 +135,65 @@ export default class SaleInvoice extends TenantModel { * Filters the due invoices. */ dueInvoices(query) { - query.where(raw('BALANCE - PAYMENT_AMOUNT > 0')); + query.where(raw("BALANCE - PAYMENT_AMOUNT > 0")); }, /** * Filters the invoices between the given date range. */ - filterDateRange(query, startDate, endDate, type = 'day') { - const dateFormat = 'YYYY-MM-DD HH:mm:ss'; + filterDateRange(query, startDate, endDate, type = "day") { + const dateFormat = "YYYY-MM-DD HH:mm:ss"; const fromDate = moment(startDate).startOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat); if (startDate) { - query.where('invoice_date', '>=', fromDate); + query.where("invoice_date", ">=", fromDate); } if (endDate) { - query.where('invoice_date', '<=', toDate); + query.where("invoice_date", "<=", toDate); } }, /** * Filters the invoices in draft status. */ draft(query) { - query.where('delivered_at', null); + query.where("delivered_at", null); }, /** * Filters the delivered invoices. */ delivered(query) { - query.whereNot('delivered_at', null); + query.whereNot("delivered_at", null); }, /** * Filters the unpaid invoices. */ unpaid(query) { - query.where(raw('PAYMENT_AMOUNT = 0')); + query.where(raw("PAYMENT_AMOUNT = 0")); }, /** * Filters the overdue invoices. */ - overdue(query, asDate = moment().format('YYYY-MM-DD')) { - query.where('due_date', '<', asDate); + overdue(query, asDate = moment().format("YYYY-MM-DD")) { + query.where("due_date", "<", asDate); }, /** * Filters the not overdue invoices. */ - notOverdue(query, asDate = moment().format('YYYY-MM-DD')) { - query.where('due_date', '>=', asDate); + notOverdue(query, asDate = moment().format("YYYY-MM-DD")) { + query.where("due_date", ">=", asDate); }, /** * Filters the partially invoices. */ partiallyPaid(query) { - query.whereNot('payment_amount', 0); - query.whereNot(raw('`PAYMENT_AMOUNT` = `BALANCE`')); + query.whereNot("payment_amount", 0); + query.whereNot(raw("`PAYMENT_AMOUNT` = `BALANCE`")); }, /** * Filters the paid invoices. */ paid(query) { - query.where(raw('PAYMENT_AMOUNT = BALANCE')); + query.where(raw("PAYMENT_AMOUNT = BALANCE")); }, /** * Filters the sale invoices from the given date. @@ -239,22 +238,22 @@ export default class SaleInvoice extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const AccountTransaction = require('models/AccountTransaction'); - const ItemEntry = require('models/ItemEntry'); - const Contact = require('models/Contact'); - const InventoryCostLotTracker = require('models/InventoryCostLotTracker'); - const PaymentReceiveEntry = require('models/PaymentReceiveEntry'); + const AccountTransaction = require("models/AccountTransaction"); + const ItemEntry = require("models/ItemEntry"); + const Contact = require("models/Contact"); + const InventoryCostLotTracker = require("models/InventoryCostLotTracker"); + const PaymentReceiveEntry = require("models/PaymentReceiveEntry"); return { entries: { relation: Model.HasManyRelation, modelClass: ItemEntry.default, join: { - from: 'sales_invoices.id', - to: 'items_entries.referenceId', + from: "sales_invoices.id", + to: "items_entries.referenceId", }, filter(builder) { - builder.where('reference_type', 'SaleInvoice'); + builder.where("reference_type", "SaleInvoice"); }, }, @@ -262,8 +261,8 @@ export default class SaleInvoice extends TenantModel { relation: Model.BelongsToOneRelation, modelClass: Contact.default, join: { - from: 'sales_invoices.customerId', - to: 'contacts.id', + from: "sales_invoices.customerId", + to: "contacts.id", }, filter(query) { query.where('contact_service', 'Customer'); @@ -276,32 +275,14 @@ export default class SaleInvoice extends TenantModel { join: { from: 'sales_invoices.id', to: 'accounts_transactions.referenceId', - }, - filter(builder) { - builder.where('reference_type', 'SaleInvoice'); - }, }, - - costTransactions: { - relation: Model.HasManyRelation, - modelClass: InventoryCostLotTracker.default, join: { from: 'sales_invoices.id', to: 'inventory_cost_lot_tracker.transactionId', }, filter(builder) { - builder.where('transaction_type', 'SaleInvoice'); - }, }, - - paymentEntries: { - relation: Model.HasManyRelation, - modelClass: PaymentReceiveEntry.default, join: { - from: 'sales_invoices.id', - to: 'payment_receives_entries.invoice_id', - }, - }, }; } @@ -311,7 +292,7 @@ export default class SaleInvoice extends TenantModel { * @param {Numeric} amount */ static async changePaymentAmount(invoiceId, amount) { - const changeMethod = amount > 0 ? 'increment' : 'decrement'; + const changeMethod = amount > 0 ? "increment" : "decrement"; await this.query() .where('id', invoiceId) @@ -324,80 +305,86 @@ export default class SaleInvoice extends TenantModel { static get fields() { return { customer: { - label: 'Customer', - column: 'customer_id', - relation: 'contacts.id', - relationColumn: 'contacts.displayName', + label: "Customer", + column: "customer_id", + relation: "contacts.id", + relationColumn: "contacts.displayName", - fieldType: 'options', - optionsResource: 'customers', - optionsKey: 'id', - optionsLable: 'displayName', + fieldType: "options", + optionsResource: "customers", + optionsKey: "id", + optionsLable: "displayName", }, invoice_date: { - label: 'Invoice date', - column: 'invoice_date', - columnType: 'date', - fieldType: 'date', + label: "Invoice date", + column: "invoice_date", + columnType: "date", + fieldType: "date", }, due_date: { - label: 'Due date', - column: 'due_date', - columnType: 'date', - fieldType: 'date', + label: "Due date", + column: "due_date", + columnType: "date", + fieldType: "date", }, invoice_no: { - label: 'Invoice No.', - column: 'invoice_no', - columnType: 'number', - fieldType: 'number', + label: "Invoice No.", + column: "invoice_no", + columnType: "number", + fieldType: "number", }, - refernece_no: { - label: 'Reference No.', - column: 'reference_no', - columnType: 'number', - fieldType: 'number', + reference_no: { + label: "Reference No.", + column: "reference_no", + columnType: "number", + fieldType: "number", }, invoice_message: { - label: 'Invoice message', - column: 'invoice_message', - columnType: 'text', - fieldType: 'text', + label: "Invoice message", + column: "invoice_message", + columnType: "text", + fieldType: "text", }, terms_conditions: { - label: 'Terms & conditions', - column: 'terms_conditions', - columnType: 'text', - fieldType: 'text', + label: "Terms & conditions", + column: "terms_conditions", + columnType: "text", + fieldType: "text", }, invoice_amount: { - label: 'Invoice amount', - column: 'invoice_amount', - columnType: 'number', - fieldType: 'number', + label: "Invoice amount", + column: "invoice_amount", + columnType: "number", + fieldType: "number", }, payment_amount: { - label: 'Payment amount', - column: 'payment_amount', - columnType: 'number', - fieldType: 'number', + label: "Payment amount", + column: "payment_amount", + columnType: "number", + fieldType: "number", + }, + balance: { + label: "Balance", + column: "balance", + columnType: "number", + fieldType: "number", }, due_amount: { - label: 'Due amount', - column: 'due_amount', - columnType: 'number', - fieldType: 'number', + label: "Due amount", + column: "due_amount", + columnType: "number", + fieldType: "number", sortQuery(query, role) { query.modify('sortByDueAmount', role.order); }, }, created_at: { - label: 'Created at', - column: 'created_at', - columnType: 'date', + label: "Created at", + column: "created_at", + columnType: "date", }, status: { - label: 'Status', + label: "Status", options: [ { key: 'draft', label: 'Draft' }, { key: 'delivered', label: 'Delivered' }, @@ -411,20 +398,20 @@ export default class SaleInvoice extends TenantModel { case 'draft': query.modify('draft'); break; - case 'delivered': - query.modify('delivered'); + case "delivered": + query.modify("delivered"); break; - case 'unpaid': - query.modify('unpaid'); + case "unpaid": + query.modify("unpaid"); break; - case 'overdue': - query.modify('overdue'); + case "overdue": + query.modify("overdue"); break; - case 'partially-paid': - query.modify('partiallyPaid'); + case "partially-paid": + query.modify("partiallyPaid"); break; - case 'paid': - query.modify('paid'); + case "paid": + query.modify("paid"); break; } }, diff --git a/server/src/models/SaleReceipt.js b/server/src/models/SaleReceipt.js index 71c623f47..5f1f1178c 100644 --- a/server/src/models/SaleReceipt.js +++ b/server/src/models/SaleReceipt.js @@ -133,7 +133,7 @@ export default class SaleReceipt extends TenantModel { }, deposit_account: { column: 'deposit_account_id', - lable: 'Deposit account', + label: 'Deposit account', relation: "accounts.id", optionsResource: "account", },