diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js
index fa41d397d..13553ae4b 100644
--- a/client/src/containers/Items/ItemForm.js
+++ b/client/src/containers/Items/ItemForm.js
@@ -6,7 +6,7 @@ import {
} from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useHistory } from 'react-router-dom';
-import { pick } from 'lodash';
+import { pick, pickBy } from 'lodash';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
@@ -23,7 +23,23 @@ import useMedia from 'hooks/useMedia';
import withItemDetail from 'containers/Items/withItemDetail';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
-import { compose } from 'utils';
+import { compose, transformToForm } from 'utils';
+
+const defaultInitialValues = {
+ active: true,
+ name: '',
+ type: 'service',
+ sku: '',
+ cost_price: '',
+ sell_price: '',
+ cost_account_id: '',
+ sell_account_id: '',
+ inventory_account_id: '',
+ category_id: '',
+ note: '',
+ sellable: true,
+ purchasable: true,
+};
/**
* Item form.
@@ -71,54 +87,61 @@ function ItemForm({
.required()
.label(formatMessage({ id: 'item_type_' })),
sku: Yup.string().trim(),
- cost_price: Yup.number(),
- sell_price: Yup.number(),
+ cost_price: Yup.number()
+ .when(['purchasable'], {
+ is: true,
+ then: Yup.number().required(),
+ otherwise: Yup.number().nullable(true),
+ }),
+ sell_price: Yup.number()
+ .when(['sellable'], {
+ is: true,
+ then: Yup.number().required(),
+ otherwise: Yup.number().nullable(true),
+ }),
cost_account_id: Yup.number()
- .required()
+ .when(['purchasable'], {
+ is: true,
+ then: Yup.number().required(),
+ otherwise: Yup.number().nullable(true),
+ })
.label(formatMessage({ id: 'cost_account_id' })),
sell_account_id: Yup.number()
- .required()
+ .when(['sellable'], {
+ is: true,
+ then: Yup.number().required(),
+ otherwise: Yup.number().nullable(),
+ })
.label(formatMessage({ id: 'sell_account_id' })),
- inventory_account_id: Yup.number().when('type', {
- is: (value) => value === 'inventory',
- then: Yup.number().required(),
- otherwise: Yup.number().nullable(),
- }),
- category_id: Yup.number().nullable(),
+ inventory_account_id: Yup.number()
+ .when(['type'], {
+ is: (value) => value === 'inventory',
+ then: Yup.number().required(),
+ otherwise: Yup.number().nullable(),
+ })
+ .label(formatMessage({ id: 'Inventory account' })),
+ category_id: Yup.number().positive().nullable(),
stock: Yup.string() || Yup.boolean(),
sellable: Yup.boolean().required(),
purchasable: Yup.boolean().required(),
});
- const defaultInitialValues = useMemo(
- () => ({
- active: true,
- name: '',
- type: 'service',
- sku: '',
- cost_price: 0,
- sell_price: 0,
- cost_account_id: null,
- sell_account_id: null,
- inventory_account_id: null,
- category_id: null,
- note: '',
- sellable: true,
- purchasable: true,
- }),
- [],
- );
+
+ /**
+ * Initial values in create and edit mode.
+ */
const initialValues = useMemo(
() => ({
- ...(itemDetail
- ? {
- ...pick(itemDetail, Object.keys(defaultInitialValues)),
- }
- : {
- ...defaultInitialValues,
- }),
+ ...defaultInitialValues,
+
+ /**
+ * 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(itemDetail, defaultInitialValues),
}),
- [itemDetail, defaultInitialValues],
+ [],
);
useEffect(() => {
@@ -141,7 +164,7 @@ function ItemForm({
'the_item_has_been_successfully_edited',
},
{
- number: itemDetail.id,
+ number: itemId,
},
),
intent: Intent.SUCCESS,
@@ -156,7 +179,7 @@ function ItemForm({
if (isNewMode) {
requestSubmitItem(form).then(onSuccess).catch(onError);
} else {
- requestEditItem(form).then(onSuccess).catch(onError);
+ requestEditItem(itemId, form).then(onSuccess).catch(onError);
}
};
diff --git a/client/src/containers/Items/ItemFormBody.js b/client/src/containers/Items/ItemFormBody.js
index d1e5aa4a4..c9b07d671 100644
--- a/client/src/containers/Items/ItemFormBody.js
+++ b/client/src/containers/Items/ItemFormBody.js
@@ -36,15 +36,15 @@ function ItemFormBody({
-
{/*------------- Sellable checkbox ------------- */}
-
+
}
+ label={
+
+
+
+ }
checked={values.sellable}
{...getFieldProps('sellable')}
/>
@@ -54,26 +54,22 @@ function ItemFormBody({
}
className={'form-group--item-selling-price'}
- intent={
- errors.selling_price && touched.selling_price && Intent.DANGER
- }
+ intent={errors.sell_price && touched.sell_price && Intent.DANGER}
helperText={
-
+
}
inline={true}
>
{
- setFieldValue('selling_price', value);
+ setFieldValue('sell_price', value);
}}
inputGroupProps={{
medium: true,
intent:
- errors.selling_price &&
- touched.selling_price &&
- Intent.DANGER,
+ errors.sell_price && touched.sell_price && Intent.DANGER,
}}
disabled={!values.sellable}
/>
@@ -107,18 +103,18 @@ function ItemFormBody({
filterByTypes={['income']}
/>
-
{/*------------- Purchasable checbox ------------- */}
-
+
}
+ label={
+
+
+
+ }
defaultChecked={values.purchasable}
{...getFieldProps('purchasable')}
/>
@@ -169,7 +165,7 @@ function ItemFormBody({
{
- setFieldValue('cost_account_id', account.id)
+ setFieldValue('cost_account_id', account.id);
}}
defaultSelectText={}
selectedAccountId={values.cost_account_id}
@@ -187,4 +183,4 @@ export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
-)(ItemFormBody);
\ No newline at end of file
+)(ItemFormBody);
diff --git a/client/src/containers/Items/ItemFormInventorySection.js b/client/src/containers/Items/ItemFormInventorySection.js
index ab6db5888..6321e7200 100644
--- a/client/src/containers/Items/ItemFormInventorySection.js
+++ b/client/src/containers/Items/ItemFormInventorySection.js
@@ -1,11 +1,17 @@
import React from 'react';
-import { FormGroup, Intent, InputGroup, Classes } from '@blueprintjs/core';
-import { AccountsSelectList, ErrorMessage, Col, Row } from 'components';
+import {
+ FormGroup,
+ Intent,
+ InputGroup,
+ Position,
+} from '@blueprintjs/core';
+import { DateInput } from '@blueprintjs/datetime';
+import { AccountsSelectList, ErrorMessage, Col, Row, Hint } from 'components';
+import { CLASSES } from 'common/classes';
import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import withAccounts from 'containers/Accounts/withAccounts';
-
-import { compose } from 'utils';
+import { compose, tansformDateValue, momentFormatter } from 'utils';
/**
* Item form inventory sections.
@@ -21,12 +27,12 @@ function ItemFormInventorySection({
}) {
return (
+
+
+
+
-
-
-
-
{/*------------- Inventory account ------------- */}
}
@@ -45,7 +51,7 @@ function ItemFormInventorySection({
className={classNames(
'form-group--item-inventory_account',
'form-group--select-list',
- Classes.FILL,
+ CLASSES.FILL,
)}
>
}
- className={'form-group--item-stock'}
+ label={}
+ labelInfo={}
+ className={'form-group--opening_quantity'}
inline={true}
>
}
+ labelInfo={}
+ className={classNames(
+ 'form-group--select-list',
+ 'form-group--opening_date',
+ CLASSES.FILL,
+ )}
+ inline={true}
+ >
+ {
+ setFieldValue('opening_date', value);
+ }}
+ popoverProps={{ position: Position.BOTTOM, minimal: true }}
+ />
+
+
+
+
+ }
+ className={'form-group--opening_average_rate'}
inline={true}
>
diff --git a/client/src/containers/Items/ItemFormPrimarySection.js b/client/src/containers/Items/ItemFormPrimarySection.js
index 99957e731..f5843cbce 100644
--- a/client/src/containers/Items/ItemFormPrimarySection.js
+++ b/client/src/containers/Items/ItemFormPrimarySection.js
@@ -39,6 +39,14 @@ function ItemFormPrimarySection({
// #withItemCategories
categoriesList,
}) {
+
+ const itemTypeHintContent = (
+ <>
+ {'Service: '}{'Services that you provide to customers. '}
+ {'Inventory: '}{'Products you buy and/or sell and that you track quantities of.'}
+ {'Non-Inventory: '}{'Products you buy and/or sell but don’t need to (or can’t) track quantities of, for example, nuts and bolts used in an installation.'}
+ >);
+
return (
@@ -47,7 +55,12 @@ function ItemFormPrimarySection({
}
- labelInfo={}
+ labelInfo={
+
+
+
+
+ }
className={'form-group--item-type'}
intent={errors.type && touched.type && Intent.DANGER}
helperText={}
@@ -56,48 +69,20 @@ function ItemFormPrimarySection({
{
- setFieldValue('item_type', value);
+ setFieldValue('type', value);
})}
- selectedValue={values.item_type}
+ selectedValue={values.type}
>
-
-
- }
+ label={}
value="service"
/>
-
-
- }
+ label={}
value="inventory"
/>
-
-
- }
+ label={}
value="non_inventory"
/>
@@ -121,7 +106,7 @@ function ItemFormPrimarySection({
{/*----------- SKU ----------*/}
}
+ label={}
labelInfo={}
className={'form-group--item-sku'}
intent={errors.sku && touched.sku && Intent.DANGER}
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index d6fbc28f7..f719a047e 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -100,7 +100,7 @@ export default {
cost_price: 'Cost Price',
inventory_information: 'Inventory Information',
inventory_account: 'Inventory Account',
- opening_stock: 'Opening Stock',
+ opening_quantity: 'Opening quantity',
save: 'Save',
save_as_draft: 'Save as Draft',
active: 'Active',
@@ -810,4 +810,6 @@ export default {
i_purchase_this_item: 'I purchase this item from a vendor.',
i_sell_this_item: 'I sell this item to a customer.',
select_display_name_as:'Select display name as',
+ opening_date: 'Opening date',
+ item_code: 'Item code',
};
diff --git a/client/src/utils.js b/client/src/utils.js
index 7c2b2f7fa..8abe4c145 100644
--- a/client/src/utils.js
+++ b/client/src/utils.js
@@ -277,4 +277,11 @@ export const itemsStartWith = (items, char) => {
export const saveInvoke = (func, ...rest) => {
return func && func(...rest);
+}
+
+export const transformToForm = (obj, emptyInitialValues) => {
+ return _.pickBy(
+ obj,
+ (val, key) => val !== null && Object.keys(emptyInitialValues).includes(key),
+ )
}
\ No newline at end of file