diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index 8a59dd093..49b9651b8 100644 --- a/client/src/containers/Items/ItemForm.js +++ b/client/src/containers/Items/ItemForm.js @@ -1,10 +1,9 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; import intl from 'react-intl-universal'; import classNames from 'classnames'; -import { defaultTo } from 'lodash'; import 'style/pages/Items/PageForm.scss'; @@ -15,43 +14,15 @@ import ItemFormBody from './ItemFormBody'; import ItemFormFloatingActions from './ItemFormFloatingActions'; import ItemFormInventorySection from './ItemFormInventorySection'; -import withSettings from 'containers/Settings/withSettings'; - -import { compose, transformToForm } from 'utils'; -import { - EditItemFormSchema, - CreateItemFormSchema, - transformItemFormData, -} from './ItemForm.schema'; +import { useItemFormInitialValues } from './utils'; +import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema'; import { useItemFormContext } from './ItemFormProvider'; -const defaultInitialValues = { - active: 1, - name: '', - type: 'service', - code: '', - cost_price: '', - sell_price: '', - cost_account_id: '', - sell_account_id: '', - inventory_account_id: '', - category_id: '', - sellable: 1, - purchasable: true, - sell_description: '', - purchase_description: '', -}; - /** * Item form. */ -function ItemForm({ - // #withSettings - preferredCostAccount, - preferredSellAccount, - preferredInventoryAccount, -}) { +export default function ItemForm() { // Item form context. const { itemId, @@ -66,32 +37,8 @@ function ItemForm({ // History context. const history = useHistory(); - /** - * Initial values in create and edit mode. - */ - const initialValues = useMemo( - () => ({ - ...defaultInitialValues, - cost_account_id: defaultTo(preferredCostAccount, ''), - sell_account_id: defaultTo(preferredSellAccount, ''), - inventory_account_id: defaultTo(preferredInventoryAccount, ''), - /** - * We only care about the fields in the form. Previously unfilled optional - * values such as `notes` come back from the API as null, so remove those - * as well. - */ - ...transformToForm( - transformItemFormData(item, defaultInitialValues), - defaultInitialValues, - ), - }), - [ - item, - preferredCostAccount, - preferredSellAccount, - preferredInventoryAccount, - ], - ); + // Initial values in create and edit mode. + const initialValues = useItemFormInitialValues(item); // Transform API errors. const transformApiErrors = (error) => { @@ -179,11 +126,3 @@ function ItemForm({ ); } - -export default compose( - withSettings(({ itemsSettings }) => ({ - preferredCostAccount: parseInt(itemsSettings?.costAccount), - preferredSellAccount: parseInt(itemsSettings?.sellAccount), - preferredInventoryAccount: parseInt(itemsSettings?.inventoryAccount), - })), -)(ItemForm); diff --git a/client/src/containers/Items/ItemFormProvider.js b/client/src/containers/Items/ItemFormProvider.js index ceab6f736..a493e6631 100644 --- a/client/src/containers/Items/ItemFormProvider.js +++ b/client/src/containers/Items/ItemFormProvider.js @@ -1,9 +1,10 @@ import React, { useEffect, createContext, useState } from 'react'; import intl from 'react-intl-universal'; -import { useLocation, useParams } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; import { useItem, + useSettingsItems, useItemsCategories, useCreateItem, useEditItem, @@ -37,6 +38,13 @@ function ItemFormProvider({ itemId, ...props }) { enabled: !!itemId || !!duplicateId, }, ); + + // Fetches item settings. + const { + isLoading: isItemsSettingsLoading, + isFetching: isItemsSettingsFetching, + } = useSettingsItems(); + // Create and edit item mutations. const { mutateAsync: editItemMutate } = useEditItem(); const { mutateAsync: createItemMutate } = useCreateItem(); @@ -65,9 +73,6 @@ function ItemFormProvider({ itemId, ...props }) { setSubmitPayload, }; - // Format message intl. - - // Change page title dispatcher. const changePageTitle = useDashboardPageTitle(); @@ -78,11 +83,14 @@ function ItemFormProvider({ itemId, ...props }) { : changePageTitle(intl.get('edit_item_details')); }, [changePageTitle, isNewMode]); + const loading = + isItemsSettingsLoading || + isAccountsLoading || + isItemsCategoriesLoading || + isItemLoading; + return ( - + ); diff --git a/client/src/containers/Items/utils.js b/client/src/containers/Items/utils.js index 1c5a61adb..d9fd04db4 100644 --- a/client/src/containers/Items/utils.js +++ b/client/src/containers/Items/utils.js @@ -1,10 +1,61 @@ +import { useMemo } from 'react'; import intl from 'react-intl-universal'; import { Intent } from '@blueprintjs/core'; +import { defaultTo } from 'lodash'; import { AppToaster } from 'components'; import { transformTableStateToQuery, defaultFastFieldShouldUpdate, } from 'utils'; +import { transformToForm } from 'utils'; +import { useSettingsSelector } from '../../hooks/state'; +import { transformItemFormData } from './ItemForm.schema'; + +const defaultInitialValues = { + active: 1, + name: '', + type: 'service', + code: '', + cost_price: '', + sell_price: '', + cost_account_id: '', + sell_account_id: '', + inventory_account_id: '', + category_id: '', + sellable: 1, + purchasable: true, + sell_description: '', + purchase_description: '', +}; + +/** + * Initial values in create and edit mode. + */ +export const useItemFormInitialValues = (item) => { + const { items: itemsSettings } = useSettingsSelector(); + + return useMemo( + () => ({ + ...defaultInitialValues, + cost_account_id: defaultTo(itemsSettings.preferredCostAccount, ''), + sell_account_id: defaultTo(itemsSettings.preferredSellAccount, ''), + inventory_account_id: defaultTo( + itemsSettings.preferredInventoryAccount, + '', + ), + /** + * We only care about the fields in the form. Previously unfilled optional + * values such as `notes` come back from the API as null, so remove those + * as well. + */ + ...transformToForm( + transformItemFormData(item, defaultInitialValues), + defaultInitialValues, + ), + }), + [item, itemsSettings], + ); +}; export const transitionItemTypeKeyToLabel = (itemTypeKey) => { const table = { @@ -130,4 +181,4 @@ export function transformItemsTableState(tableState) { ...transformTableStateToQuery(tableState), inactive_mode: tableState.inactiveMode, }; -} \ No newline at end of file +} diff --git a/client/src/containers/Preferences/Item/Item.js b/client/src/containers/Preferences/Item/Item.js deleted file mode 100644 index bc2e888ae..000000000 --- a/client/src/containers/Preferences/Item/Item.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import ItemFormPage from './ItemFormPage'; -import { ItemFormProvider } from './ItemFormProvider'; - -/** - * items preferences. - */ -export default function ItemsPreferences() { - return ( - - - - ); -} diff --git a/client/src/containers/Preferences/Item/Item.schema.js b/client/src/containers/Preferences/Item/Item.schema.js deleted file mode 100644 index 5a032f4d1..000000000 --- a/client/src/containers/Preferences/Item/Item.schema.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Yup from 'yup'; - -const Schema = Yup.object().shape({ - sell_account: Yup.number().nullable(), - cost_account: Yup.number().nullable(), - inventory_account: Yup.number().nullable(), -}); - -export const ItemPreferencesSchema = Schema; diff --git a/client/src/containers/Preferences/Item/ItemPreferences.schema.js b/client/src/containers/Preferences/Item/ItemPreferences.schema.js new file mode 100644 index 000000000..456a5ac99 --- /dev/null +++ b/client/src/containers/Preferences/Item/ItemPreferences.schema.js @@ -0,0 +1,9 @@ +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + preferred_sell_account: Yup.number().nullable(), + preferred_cost_account: Yup.number().nullable(), + preferred_inventory_account: Yup.number().nullable(), +}); + +export const ItemPreferencesSchema = Schema; diff --git a/client/src/containers/Preferences/Item/ItemForm.js b/client/src/containers/Preferences/Item/ItemPreferencesForm.js similarity index 86% rename from client/src/containers/Preferences/Item/ItemForm.js rename to client/src/containers/Preferences/Item/ItemPreferencesForm.js index 355adef02..4f82fcf95 100644 --- a/client/src/containers/Preferences/Item/ItemForm.js +++ b/client/src/containers/Preferences/Item/ItemPreferencesForm.js @@ -1,21 +1,23 @@ import React from 'react'; import { Form, FastField, useFormikContext } from 'formik'; import { FormGroup, Button, Intent } from '@blueprintjs/core'; -import { AccountsSelectList, FieldRequiredHint } from 'components'; -import { FormattedMessage as T } from 'components'; -import intl from 'react-intl-universal'; import { useHistory } from 'react-router-dom'; +import { + AccountsSelectList, + FieldRequiredHint, + FormattedMessage as T, +} from 'components'; import { inputIntent } from 'utils'; import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'common/accountTypes'; -import { useItemFormContext } from './ItemFormProvider'; +import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider'; /** - * item form preferences. + * Item preferences form. */ export default function ItemForm() { const history = useHistory(); - const { accounts } = useItemFormContext(); + const { accounts } = useItemPreferencesFormContext(); const { isSubmitting } = useFormikContext(); @@ -26,7 +28,7 @@ export default function ItemForm() { return (
{/* ----------- preferred sell account ----------- */} - + {({ form: { values, setFieldValue }, field: { value }, @@ -51,7 +53,7 @@ export default function ItemForm() { { - setFieldValue('sell_account', id); + setFieldValue('preferred_sell_account', id); }} selectedAccountId={value} defaultSelectText={} @@ -62,7 +64,7 @@ export default function ItemForm() { {/* ----------- preferred cost account ----------- */} - + {({ form: { values, setFieldValue }, field: { value }, @@ -87,7 +89,7 @@ export default function ItemForm() { { - setFieldValue('cost_account', id); + setFieldValue('preferred_cost_account', id); }} selectedAccountId={value} defaultSelectText={} @@ -98,7 +100,7 @@ export default function ItemForm() { {/* ----------- preferred inventory account ----------- */} - + {({ form: { values, setFieldValue }, field: { value }, @@ -123,7 +125,7 @@ export default function ItemForm() { { - setFieldValue('inventory_account', id); + setFieldValue('preferred_inventory_account', id); }} selectedAccountId={value} defaultSelectText={} diff --git a/client/src/containers/Preferences/Item/ItemFormPage.js b/client/src/containers/Preferences/Item/ItemPreferencesFormPage.js similarity index 76% rename from client/src/containers/Preferences/Item/ItemFormPage.js rename to client/src/containers/Preferences/Item/ItemPreferencesFormPage.js index e7f37aa6d..f9b27fd63 100644 --- a/client/src/containers/Preferences/Item/ItemFormPage.js +++ b/client/src/containers/Preferences/Item/ItemPreferencesFormPage.js @@ -3,10 +3,10 @@ import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from 'components'; import intl from 'react-intl-universal'; -import { ItemPreferencesSchema } from './Item.schema'; -import ItemForm from './ItemForm'; +import { ItemPreferencesSchema } from './ItemPreferences.schema'; +import ItemPreferencesForm from './ItemPreferencesForm'; -import { useItemFormContext } from './ItemFormProvider'; +import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider'; import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withSettings from 'containers/Settings/withSettings'; import { compose, optionsMapToArray, transformGeneralSettings } from 'utils'; @@ -14,20 +14,20 @@ import { compose, optionsMapToArray, transformGeneralSettings } from 'utils'; import 'style/pages/Preferences/Accounting.scss'; // item form page preferences. -function ItemFormPage({ +function ItemPreferencesFormPage({ // #withSettings itemsSettings, // #withDashboardActions changePreferencesPageTitle, }) { - - const { saveSettingMutate } = useItemFormContext(); + const { saveSettingMutate } = useItemPreferencesFormContext(); + // Initial values. const initialValues = { - sell_account: '', - cost_account: '', - inventory_account: '', + preferred_sell_account: '', + preferred_cost_account: '', + preferred_inventory_account: '', ...transformGeneralSettings(itemsSettings), }; @@ -59,7 +59,7 @@ function ItemFormPage({ initialValues={initialValues} validationSchema={ItemPreferencesSchema} onSubmit={handleFormSubmit} - component={ItemForm} + component={ItemPreferencesForm} /> ); } @@ -67,4 +67,4 @@ function ItemFormPage({ export default compose( withSettings(({ itemsSettings }) => ({ itemsSettings })), withDashboardActions, -)(ItemFormPage); +)(ItemPreferencesFormPage); diff --git a/client/src/containers/Preferences/Item/ItemFormProvider.js b/client/src/containers/Preferences/Item/ItemPreferencesFormProvider.js similarity index 68% rename from client/src/containers/Preferences/Item/ItemFormProvider.js rename to client/src/containers/Preferences/Item/ItemPreferencesFormProvider.js index 81b6fa2ce..2b6162693 100644 --- a/client/src/containers/Preferences/Item/ItemFormProvider.js +++ b/client/src/containers/Preferences/Item/ItemPreferencesFormProvider.js @@ -2,7 +2,7 @@ import React, { useContext, createContext } from 'react'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; -import { useAccounts, useSaveSettings } from 'hooks/query'; +import { useSettingsItems, useAccounts, useSaveSettings } from 'hooks/query'; import PreferencesPageLoader from '../PreferencesPageLoader'; const ItemFormContext = createContext(); @@ -11,10 +11,15 @@ const ItemFormContext = createContext(); * Item data provider. */ -function ItemFormProvider({ ...props }) { +function ItemPreferencesFormProvider({ ...props }) { // Fetches the accounts list. const { isLoading: isAccountsLoading, data: accounts } = useAccounts(); + const { + isLoading: isItemsSettingsLoading, + isFetching: isItemsSettingsFetching, + } = useSettingsItems(); + // Save Organization Settings. const { mutateAsync: saveSettingMutate } = useSaveSettings(); @@ -24,7 +29,7 @@ function ItemFormProvider({ ...props }) { saveSettingMutate, }; - const isLoading = isAccountsLoading; + const isLoading = isAccountsLoading || isItemsSettingsLoading; return (
useContext(ItemFormContext); +const useItemPreferencesFormContext = () => useContext(ItemFormContext); -export { useItemFormContext, ItemFormProvider }; +export { useItemPreferencesFormContext, ItemPreferencesFormProvider }; diff --git a/client/src/containers/Preferences/Item/index.js b/client/src/containers/Preferences/Item/index.js new file mode 100644 index 000000000..097610d4f --- /dev/null +++ b/client/src/containers/Preferences/Item/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import ItemPreferencesFormPage from './ItemPreferencesFormPage'; +import { ItemPreferencesFormProvider } from './ItemPreferencesFormProvider'; + +/** + * items preferences. + */ +export default function ItemsPreferences() { + return ( + + + + ); +} diff --git a/client/src/hooks/query/settings.js b/client/src/hooks/query/settings.js index 545dc8eb8..c9f95fb6d 100644 --- a/client/src/hooks/query/settings.js +++ b/client/src/hooks/query/settings.js @@ -99,4 +99,16 @@ export function useSettingsManualJournals(props) { { group: 'sale_receipts' }, props, ); +} + +/** + * Retrieve sale receipts settings. + * @param {*} props + */ + export function useSettingsItems(props) { + return useSettingsQuery( + [t.SETTING, t.SETTING_ITEMS], + { group: 'items' }, + props, + ); } \ No newline at end of file diff --git a/client/src/hooks/query/types.js b/client/src/hooks/query/types.js index 2b141bd1d..9461b93db 100644 --- a/client/src/hooks/query/types.js +++ b/client/src/hooks/query/types.js @@ -99,6 +99,7 @@ const SETTING = { SETTING_RECEIPTS: 'SETTING_RECEIPTS', SETTING_PAYMENT_RECEIVES: 'SETTING_PAYMENT_RECEIVES', SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS', + SETTING_ITEMS: 'SETTING_ITEMS' }; const ORGANIZATIONS = { diff --git a/client/src/hooks/state/settings.js b/client/src/hooks/state/settings.js index b26393c21..0b7c4e2d9 100644 --- a/client/src/hooks/state/settings.js +++ b/client/src/hooks/state/settings.js @@ -1,12 +1,21 @@ -import { useCallback } from "react"; -import { useDispatch } from "react-redux"; +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { setSettings } from 'store/settings/settings.actions'; - export const useSetSettings = () => { - const dispatch = useDispatch(); + const dispatch = useDispatch(); - return useCallback((settings) => { - dispatch(setSettings(settings)); - }, [dispatch]); -}; \ No newline at end of file + return useCallback( + (settings) => { + dispatch(setSettings(settings)); + }, + [dispatch], + ); +}; + +/** + * Retrieve the authentication token. + */ +export const useSettingsSelector = () => { + return useSelector((state) => state.settings.data); +}; diff --git a/client/src/routes/preferences.js b/client/src/routes/preferences.js index b83aad542..cc539a664 100644 --- a/client/src/routes/preferences.js +++ b/client/src/routes/preferences.js @@ -3,7 +3,7 @@ import Users from 'containers/Preferences/Users/Users'; import Accountant from 'containers/Preferences/Accountant/Accountant'; // import Accounts from 'containers/Preferences/Accounts/Accounts'; import Currencies from 'containers/Preferences/Currencies/Currencies'; -import Item from 'containers/Preferences/Item/Item'; +import Item from 'containers/Preferences/Item'; import DefaultRoute from '../containers/Preferences/DefaultRoute'; const BASE_URL = '/preferences'; diff --git a/server/src/data/options.js b/server/src/data/options.js index ebbc0d707..8e65196ad 100644 --- a/server/src/data/options.js +++ b/server/src/data/options.js @@ -93,21 +93,21 @@ export default { auto_increment: { type: 'boolean', }, - deposit_account: { + preferred_deposit_account: { type: 'number', }, - advance_deposit: { + preferred_advance_deposit: { type: 'number', }, }, items: { - sell_account: { + preferred_sell_account: { type: 'number', }, - cost_account: { + preferred_cost_account: { type: 'number', }, - inventory_account: { + preferred_inventory_account: { type: 'number', }, }, diff --git a/server/src/database/seeds/core/20200810121809_seed_settings.js b/server/src/database/seeds/core/20200810121809_seed_settings.js index d72998dff..cf5e8eb4e 100644 --- a/server/src/database/seeds/core/20200810121809_seed_settings.js +++ b/server/src/database/seeds/core/20200810121809_seed_settings.js @@ -16,10 +16,6 @@ exports.up = (knex) => { { group: 'sales_invoices', key: 'number_prefix', value: 'INV-' }, { group: 'sales_invoices', key: 'auto_increment', value: true }, - { group: 'sales_invoices', key: 'next_number', value: '00001' }, - { group: 'sales_invoices', key: 'number_prefix', value: 'INV-' }, - { group: 'sales_invoices', key: 'auto_increment', value: true }, - // Sale receipts settings. { group: 'sales_receipts', key: 'next_number', value: '00001' }, { group: 'sales_receipts', key: 'number_prefix', value: 'REC-' }, diff --git a/server/src/database/seeds/core/20200810121909_seed_items_settings.js b/server/src/database/seeds/core/20200810121909_seed_items_settings.js new file mode 100644 index 000000000..c86d53fba --- /dev/null +++ b/server/src/database/seeds/core/20200810121909_seed_items_settings.js @@ -0,0 +1,27 @@ +exports.up = async (knex) => { + const costAccount = await knex('accounts') + .where('slug', 'cost-of-goods-sold') + .first(); + + const sellAccount = await knex('accounts') + .where('slug', 'sales-of-product-income') + .first(); + + const inventoryAccount = await knex('accounts') + .where('slug', 'inventory-asset') + .first(); + + const settings = [ + // Items settings. + { group: 'items', key: 'preferred_sell_account', value: sellAccount?.id }, + { group: 'items', key: 'preferred_cost_account', value: costAccount?.id }, + { + group: 'items', + key: 'preferred_inventory_account', + value: inventoryAccount?.id, + }, + ]; + return knex('settings').insert(settings); +}; + +exports.down = (knex) => {};