BIG-26: fix items preferences form.

This commit is contained in:
a.bouhuolia
2021-09-12 13:14:15 +02:00
parent b3a96ace71
commit cdc75a01af
17 changed files with 195 additions and 145 deletions

View File

@@ -1,10 +1,9 @@
import React, { useMemo } from 'react'; import React from 'react';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import classNames from 'classnames'; import classNames from 'classnames';
import { defaultTo } from 'lodash';
import 'style/pages/Items/PageForm.scss'; import 'style/pages/Items/PageForm.scss';
@@ -15,43 +14,15 @@ import ItemFormBody from './ItemFormBody';
import ItemFormFloatingActions from './ItemFormFloatingActions'; import ItemFormFloatingActions from './ItemFormFloatingActions';
import ItemFormInventorySection from './ItemFormInventorySection'; import ItemFormInventorySection from './ItemFormInventorySection';
import withSettings from 'containers/Settings/withSettings'; import { useItemFormInitialValues } from './utils';
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
import { compose, transformToForm } from 'utils';
import {
EditItemFormSchema,
CreateItemFormSchema,
transformItemFormData,
} from './ItemForm.schema';
import { useItemFormContext } from './ItemFormProvider'; 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. * Item form.
*/ */
function ItemForm({ export default function ItemForm() {
// #withSettings
preferredCostAccount,
preferredSellAccount,
preferredInventoryAccount,
}) {
// Item form context. // Item form context.
const { const {
itemId, itemId,
@@ -66,32 +37,8 @@ function ItemForm({
// History context. // History context.
const history = useHistory(); const history = useHistory();
/** // Initial values in create and edit mode.
* Initial values in create and edit mode. const initialValues = useItemFormInitialValues(item);
*/
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,
],
);
// Transform API errors. // Transform API errors.
const transformApiErrors = (error) => { const transformApiErrors = (error) => {
@@ -179,11 +126,3 @@ function ItemForm({
</div> </div>
); );
} }
export default compose(
withSettings(({ itemsSettings }) => ({
preferredCostAccount: parseInt(itemsSettings?.costAccount),
preferredSellAccount: parseInt(itemsSettings?.sellAccount),
preferredInventoryAccount: parseInt(itemsSettings?.inventoryAccount),
})),
)(ItemForm);

View File

@@ -1,9 +1,10 @@
import React, { useEffect, createContext, useState } from 'react'; import React, { useEffect, createContext, useState } from 'react';
import intl from 'react-intl-universal'; 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 DashboardInsider from 'components/Dashboard/DashboardInsider';
import { import {
useItem, useItem,
useSettingsItems,
useItemsCategories, useItemsCategories,
useCreateItem, useCreateItem,
useEditItem, useEditItem,
@@ -37,6 +38,13 @@ function ItemFormProvider({ itemId, ...props }) {
enabled: !!itemId || !!duplicateId, enabled: !!itemId || !!duplicateId,
}, },
); );
// Fetches item settings.
const {
isLoading: isItemsSettingsLoading,
isFetching: isItemsSettingsFetching,
} = useSettingsItems();
// Create and edit item mutations. // Create and edit item mutations.
const { mutateAsync: editItemMutate } = useEditItem(); const { mutateAsync: editItemMutate } = useEditItem();
const { mutateAsync: createItemMutate } = useCreateItem(); const { mutateAsync: createItemMutate } = useCreateItem();
@@ -65,9 +73,6 @@ function ItemFormProvider({ itemId, ...props }) {
setSubmitPayload, setSubmitPayload,
}; };
// Format message intl.
// Change page title dispatcher. // Change page title dispatcher.
const changePageTitle = useDashboardPageTitle(); const changePageTitle = useDashboardPageTitle();
@@ -78,11 +83,14 @@ function ItemFormProvider({ itemId, ...props }) {
: changePageTitle(intl.get('edit_item_details')); : changePageTitle(intl.get('edit_item_details'));
}, [changePageTitle, isNewMode]); }, [changePageTitle, isNewMode]);
const loading =
isItemsSettingsLoading ||
isAccountsLoading ||
isItemsCategoriesLoading ||
isItemLoading;
return ( return (
<DashboardInsider <DashboardInsider loading={loading} name={'item-form'}>
loading={isAccountsLoading || isItemsCategoriesLoading || isItemLoading}
name={'item-form'}
>
<ItemFormContext.Provider value={provider} {...props} /> <ItemFormContext.Provider value={provider} {...props} />
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -1,10 +1,61 @@
import { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { defaultTo } from 'lodash';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { import {
transformTableStateToQuery, transformTableStateToQuery,
defaultFastFieldShouldUpdate, defaultFastFieldShouldUpdate,
} from 'utils'; } 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) => { export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
const table = { const table = {

View File

@@ -1,14 +0,0 @@
import React from 'react';
import ItemFormPage from './ItemFormPage';
import { ItemFormProvider } from './ItemFormProvider';
/**
* items preferences.
*/
export default function ItemsPreferences() {
return (
<ItemFormProvider>
<ItemFormPage />
</ItemFormProvider>
);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,21 +1,23 @@
import React from 'react'; import React from 'react';
import { Form, FastField, useFormikContext } from 'formik'; import { Form, FastField, useFormikContext } from 'formik';
import { FormGroup, Button, Intent } from '@blueprintjs/core'; 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 { useHistory } from 'react-router-dom';
import {
AccountsSelectList,
FieldRequiredHint,
FormattedMessage as T,
} from 'components';
import { inputIntent } from 'utils'; import { inputIntent } from 'utils';
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'common/accountTypes'; 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() { export default function ItemForm() {
const history = useHistory(); const history = useHistory();
const { accounts } = useItemFormContext(); const { accounts } = useItemPreferencesFormContext();
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
@@ -26,7 +28,7 @@ export default function ItemForm() {
return ( return (
<Form> <Form>
{/* ----------- preferred sell account ----------- */} {/* ----------- preferred sell account ----------- */}
<FastField name={'sell_account'}> <FastField name={'preferred_sell_account'}>
{({ {({
form: { values, setFieldValue }, form: { values, setFieldValue },
field: { value }, field: { value },
@@ -51,7 +53,7 @@ export default function ItemForm() {
<AccountsSelectList <AccountsSelectList
accounts={accounts} accounts={accounts}
onAccountSelected={({ id }) => { onAccountSelected={({ id }) => {
setFieldValue('sell_account', id); setFieldValue('preferred_sell_account', id);
}} }}
selectedAccountId={value} selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />} defaultSelectText={<T id={'select_payment_account'} />}
@@ -62,7 +64,7 @@ export default function ItemForm() {
</FastField> </FastField>
{/* ----------- preferred cost account ----------- */} {/* ----------- preferred cost account ----------- */}
<FastField name={'cost_account'}> <FastField name={'preferred_cost_account'}>
{({ {({
form: { values, setFieldValue }, form: { values, setFieldValue },
field: { value }, field: { value },
@@ -87,7 +89,7 @@ export default function ItemForm() {
<AccountsSelectList <AccountsSelectList
accounts={accounts} accounts={accounts}
onAccountSelected={({ id }) => { onAccountSelected={({ id }) => {
setFieldValue('cost_account', id); setFieldValue('preferred_cost_account', id);
}} }}
selectedAccountId={value} selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />} defaultSelectText={<T id={'select_payment_account'} />}
@@ -98,7 +100,7 @@ export default function ItemForm() {
</FastField> </FastField>
{/* ----------- preferred inventory account ----------- */} {/* ----------- preferred inventory account ----------- */}
<FastField name={'inventory_account'}> <FastField name={'preferred_inventory_account'}>
{({ {({
form: { values, setFieldValue }, form: { values, setFieldValue },
field: { value }, field: { value },
@@ -123,7 +125,7 @@ export default function ItemForm() {
<AccountsSelectList <AccountsSelectList
accounts={accounts} accounts={accounts}
onAccountSelected={({ id }) => { onAccountSelected={({ id }) => {
setFieldValue('inventory_account', id); setFieldValue('preferred_inventory_account', id);
}} }}
selectedAccountId={value} selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />} defaultSelectText={<T id={'select_payment_account'} />}

View File

@@ -3,10 +3,10 @@ import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { ItemPreferencesSchema } from './Item.schema'; import { ItemPreferencesSchema } from './ItemPreferences.schema';
import ItemForm from './ItemForm'; import ItemPreferencesForm from './ItemPreferencesForm';
import { useItemFormContext } from './ItemFormProvider'; import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import { compose, optionsMapToArray, transformGeneralSettings } from 'utils'; import { compose, optionsMapToArray, transformGeneralSettings } from 'utils';
@@ -14,20 +14,20 @@ import { compose, optionsMapToArray, transformGeneralSettings } from 'utils';
import 'style/pages/Preferences/Accounting.scss'; import 'style/pages/Preferences/Accounting.scss';
// item form page preferences. // item form page preferences.
function ItemFormPage({ function ItemPreferencesFormPage({
// #withSettings // #withSettings
itemsSettings, itemsSettings,
// #withDashboardActions // #withDashboardActions
changePreferencesPageTitle, changePreferencesPageTitle,
}) { }) {
const { saveSettingMutate } = useItemPreferencesFormContext();
const { saveSettingMutate } = useItemFormContext(); // Initial values.
const initialValues = { const initialValues = {
sell_account: '', preferred_sell_account: '',
cost_account: '', preferred_cost_account: '',
inventory_account: '', preferred_inventory_account: '',
...transformGeneralSettings(itemsSettings), ...transformGeneralSettings(itemsSettings),
}; };
@@ -59,7 +59,7 @@ function ItemFormPage({
initialValues={initialValues} initialValues={initialValues}
validationSchema={ItemPreferencesSchema} validationSchema={ItemPreferencesSchema}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
component={ItemForm} component={ItemPreferencesForm}
/> />
); );
} }
@@ -67,4 +67,4 @@ function ItemFormPage({
export default compose( export default compose(
withSettings(({ itemsSettings }) => ({ itemsSettings })), withSettings(({ itemsSettings }) => ({ itemsSettings })),
withDashboardActions, withDashboardActions,
)(ItemFormPage); )(ItemPreferencesFormPage);

View File

@@ -2,7 +2,7 @@ import React, { useContext, createContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { useAccounts, useSaveSettings } from 'hooks/query'; import { useSettingsItems, useAccounts, useSaveSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader'; import PreferencesPageLoader from '../PreferencesPageLoader';
const ItemFormContext = createContext(); const ItemFormContext = createContext();
@@ -11,10 +11,15 @@ const ItemFormContext = createContext();
* Item data provider. * Item data provider.
*/ */
function ItemFormProvider({ ...props }) { function ItemPreferencesFormProvider({ ...props }) {
// Fetches the accounts list. // Fetches the accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts(); const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
const {
isLoading: isItemsSettingsLoading,
isFetching: isItemsSettingsFetching,
} = useSettingsItems();
// Save Organization Settings. // Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings(); const { mutateAsync: saveSettingMutate } = useSaveSettings();
@@ -24,7 +29,7 @@ function ItemFormProvider({ ...props }) {
saveSettingMutate, saveSettingMutate,
}; };
const isLoading = isAccountsLoading; const isLoading = isAccountsLoading || isItemsSettingsLoading;
return ( return (
<div <div
@@ -44,6 +49,6 @@ function ItemFormProvider({ ...props }) {
); );
} }
const useItemFormContext = () => useContext(ItemFormContext); const useItemPreferencesFormContext = () => useContext(ItemFormContext);
export { useItemFormContext, ItemFormProvider }; export { useItemPreferencesFormContext, ItemPreferencesFormProvider };

View File

@@ -0,0 +1,14 @@
import React from 'react';
import ItemPreferencesFormPage from './ItemPreferencesFormPage';
import { ItemPreferencesFormProvider } from './ItemPreferencesFormProvider';
/**
* items preferences.
*/
export default function ItemsPreferences() {
return (
<ItemPreferencesFormProvider>
<ItemPreferencesFormPage />
</ItemPreferencesFormProvider>
);
}

View File

@@ -100,3 +100,15 @@ export function useSettingsManualJournals(props) {
props, props,
); );
} }
/**
* Retrieve sale receipts settings.
* @param {*} props
*/
export function useSettingsItems(props) {
return useSettingsQuery(
[t.SETTING, t.SETTING_ITEMS],
{ group: 'items' },
props,
);
}

View File

@@ -99,6 +99,7 @@ const SETTING = {
SETTING_RECEIPTS: 'SETTING_RECEIPTS', SETTING_RECEIPTS: 'SETTING_RECEIPTS',
SETTING_PAYMENT_RECEIVES: 'SETTING_PAYMENT_RECEIVES', SETTING_PAYMENT_RECEIVES: 'SETTING_PAYMENT_RECEIVES',
SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS', SETTING_MANUAL_JOURNALS: 'SETTING_MANUAL_JOURNALS',
SETTING_ITEMS: 'SETTING_ITEMS'
}; };
const ORGANIZATIONS = { const ORGANIZATIONS = {

View File

@@ -1,12 +1,21 @@
import { useCallback } from "react"; import { useCallback } from 'react';
import { useDispatch } from "react-redux"; import { useDispatch, useSelector } from 'react-redux';
import { setSettings } from 'store/settings/settings.actions'; import { setSettings } from 'store/settings/settings.actions';
export const useSetSettings = () => { export const useSetSettings = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
return useCallback((settings) => { return useCallback(
(settings) => {
dispatch(setSettings(settings)); dispatch(setSettings(settings));
}, [dispatch]); },
[dispatch],
);
};
/**
* Retrieve the authentication token.
*/
export const useSettingsSelector = () => {
return useSelector((state) => state.settings.data);
}; };

View File

@@ -3,7 +3,7 @@ import Users from 'containers/Preferences/Users/Users';
import Accountant from 'containers/Preferences/Accountant/Accountant'; import Accountant from 'containers/Preferences/Accountant/Accountant';
// import Accounts from 'containers/Preferences/Accounts/Accounts'; // import Accounts from 'containers/Preferences/Accounts/Accounts';
import Currencies from 'containers/Preferences/Currencies/Currencies'; 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'; import DefaultRoute from '../containers/Preferences/DefaultRoute';
const BASE_URL = '/preferences'; const BASE_URL = '/preferences';

View File

@@ -93,21 +93,21 @@ export default {
auto_increment: { auto_increment: {
type: 'boolean', type: 'boolean',
}, },
deposit_account: { preferred_deposit_account: {
type: 'number', type: 'number',
}, },
advance_deposit: { preferred_advance_deposit: {
type: 'number', type: 'number',
}, },
}, },
items: { items: {
sell_account: { preferred_sell_account: {
type: 'number', type: 'number',
}, },
cost_account: { preferred_cost_account: {
type: 'number', type: 'number',
}, },
inventory_account: { preferred_inventory_account: {
type: 'number', type: 'number',
}, },
}, },

View File

@@ -16,10 +16,6 @@ exports.up = (knex) => {
{ group: 'sales_invoices', key: 'number_prefix', value: 'INV-' }, { group: 'sales_invoices', key: 'number_prefix', value: 'INV-' },
{ group: 'sales_invoices', key: 'auto_increment', value: true }, { 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. // Sale receipts settings.
{ group: 'sales_receipts', key: 'next_number', value: '00001' }, { group: 'sales_receipts', key: 'next_number', value: '00001' },
{ group: 'sales_receipts', key: 'number_prefix', value: 'REC-' }, { group: 'sales_receipts', key: 'number_prefix', value: 'REC-' },

View File

@@ -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) => {};