diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index fa41d397d..1576aee72 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,24 @@ 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 +88,60 @@ 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(() => { @@ -127,6 +150,14 @@ function ItemForm({ : changePageTitle(formatMessage({ id: 'new_item' })); }, [changePageTitle, isNewMode, formatMessage]); + const transformApiErrors = (errors) => { + const fields = {}; + if (errors.find(e => e.type === 'ITEM.NAME.ALREADY.EXISTS')) { + fields.name = formatMessage({ id: 'the_name_used_before' }) + } + return fields; + } + // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, resetForm, setErrors }) => { setSubmitting(true); @@ -141,7 +172,7 @@ function ItemForm({ 'the_item_has_been_successfully_edited', }, { - number: itemDetail.id, + number: itemId, }, ), intent: Intent.SUCCESS, @@ -150,13 +181,18 @@ function ItemForm({ history.push('/items'); queryCache.removeQueries(['items-table']); }; - const onError = (response) => { + const onError = ({ response }) => { setSubmitting(false); + + if (response.data.errors) { + const _errors = transformApiErrors(response.data.errors); + setErrors({ ..._errors }); + } }; if (isNewMode) { requestSubmitItem(form).then(onSuccess).catch(onError); } else { - requestEditItem(form).then(onSuccess).catch(onError); + requestEditItem(itemId, form).then(onSuccess).catch(onError); } }; @@ -181,7 +217,7 @@ function ItemForm({ } else { changePageSubtitle(''); } - }, [values.item_type]); + }, [values.item_type, changePageSubtitle, formatMessage]); const initialAttachmentFiles = useMemo(() => { return itemDetail && itemDetail.media @@ -217,24 +253,24 @@ function ItemForm({