diff --git a/package.json b/package.json index beaf25220..8778e01b9 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "orders.holding-summary": "1.0", "order-lines": "2.0 3.0", "organizations.organizations": "1.0", + "servint": "2.0 3.0", "pieces": "3.0", "titles": "1.2" }, @@ -484,6 +485,14 @@ ], "visible": true }, + { + "permissionName": "ui-inventory.settings.numberGenerator.manage", + "displayName": "Settings (Inventory): Manage number generator options", + "subPermissions": [ + "settings.users.enabled" + ], + "visible": true + }, { "permissionName": "ui-inventory.instance.view", "displayName": "Inventory: View instances, holdings, and items", @@ -868,6 +877,7 @@ "@babel/preset-react": "^7.9.0", "@folio/eslint-config-stripes": "^7.0.0", "@folio/jest-config-stripes": "^2.0.0", + "@folio/service-interaction": "^3.0.0", "@folio/stripes": "^9.0.0", "@folio/stripes-cli": "^3.0.0", "@folio/stripes-components": "^12.0.0", @@ -915,6 +925,7 @@ "use-session-storage-state": "^18.2.0" }, "peerDependencies": { + "@folio/service-interaction": "^3.0.0", "@folio/stripes": "^9.0.0", "@folio/stripes-marc-components": "^1.0.0", "react": "^18.2.0", diff --git a/src/Holding/CreateHolding/CreateHolding.js b/src/Holding/CreateHolding/CreateHolding.js index 69e968450..05b490e4f 100644 --- a/src/Holding/CreateHolding/CreateHolding.js +++ b/src/Holding/CreateHolding/CreateHolding.js @@ -13,7 +13,7 @@ import { import { LoadingView } from '@folio/stripes/components'; import { useInstance } from '../../common/hooks'; -import { useCallout } from '../../hooks'; +import { useCallout, useConfigurationQuery } from '../../hooks'; import HoldingsForm from '../../edit/holdings/HoldingsForm'; import { switchAffiliation } from '../../utils'; @@ -26,6 +26,8 @@ const CreateHolding = ({ mutator, }) => { const callout = useCallout(); + const { configs } = useConfigurationQuery('number_generator'); + const { instance, isLoading: isInstanceLoading } = useInstance(instanceId); const sourceId = referenceData.holdingsSourcesByName?.FOLIO?.id; const tenantFrom = location?.state?.tenantFrom || stripes.okapi.tenant; @@ -65,6 +67,7 @@ const CreateHolding = ({ return ( ({ ...jest.requireActual('../../hooks'), useCallout: () => ({ sendCallout: jest.fn() }), + useConfigurationQuery: () => ({ configs: {} }) })); jest.mock('../../common/hooks', () => ({ diff --git a/src/Holding/EditHolding/EditHolding.js b/src/Holding/EditHolding/EditHolding.js index 46ea3895d..8ed302d9a 100644 --- a/src/Holding/EditHolding/EditHolding.js +++ b/src/Holding/EditHolding/EditHolding.js @@ -12,6 +12,7 @@ import { } from '../../common/hooks'; import { useCallout, + useConfigurationQuery, useHoldingItemsQuery, useHoldingMutation, } from '../../hooks'; @@ -31,6 +32,8 @@ const EditHolding = ({ referenceTables, }) => { const callout = useCallout(); + const { configs } = useConfigurationQuery('number_generator'); + const { search, state: { @@ -38,6 +41,7 @@ const EditHolding = ({ tenantFrom, } = {}, } = location; + const stripes = useStripes(); const [httpError, setHttpError] = useState(); const { instance, isLoading: isInstanceLoading } = useInstanceQuery(instanceId); @@ -46,7 +50,6 @@ const EditHolding = ({ searchParams: { limit: 1 }, }); - const isMARCRecord = useMemo(() => ( referenceTables?.holdingsSources?.find(source => source.id === holding?.sourceId)?.name === 'MARC' ), [holding]); @@ -95,6 +98,7 @@ const EditHolding = ({ return ( <> jest.fn().mockReturnValue('H jest.mock('../../hooks', () => ({ ...jest.requireActual('../../hooks'), useCallout: () => jest.fn().mockReturnValue({ sendCallout: jest.fn() }), + useConfigurationQuery: () => ({ configs: {} }), useHoldingItemsQuery: jest.fn().mockReturnValue({ totalRecords: 1, isLoading: false }), useHoldingMutation: jest.fn().mockReturnValue({ mutateHolding: jest.fn() }), })); diff --git a/src/Item/CreateItem/CreateItem.js b/src/Item/CreateItem/CreateItem.js index ae24cecc2..4997497b1 100644 --- a/src/Item/CreateItem/CreateItem.js +++ b/src/Item/CreateItem/CreateItem.js @@ -14,8 +14,8 @@ import { useHolding, } from '../../common'; import ItemForm from '../../edit/items/ItemForm'; -import useCallout from '../../hooks/useCallout'; import { useItemMutation } from '../hooks'; +import { useCallout, useConfigurationQuery } from '../../hooks'; import { switchAffiliation } from '../../utils'; const CreateItem = ({ @@ -38,6 +38,7 @@ const CreateItem = ({ const { isLoading: isHoldingLoading, holding } = useHolding(holdingId, { tenantId: tenantTo }); const callout = useCallout(); const stripes = useStripes(); + const { configs } = useConfigurationQuery('number_generator'); const initialValues = useMemo(() => ({ status: { name: 'Available' }, @@ -79,6 +80,7 @@ const CreateItem = ({ return ( jest.fn().mockReturnValue('ItemForm')); -jest.mock('../../hooks/useCallout', () => jest.fn().mockReturnValue({ sendCallout: jest.fn() })); + +jest.mock('../../hooks', () => ({ + ...jest.requireActual('../../hooks'), + useCallout: () => jest.fn().mockReturnValue({ sendCallout: jest.fn() }), + useConfigurationQuery: () => ({ configs: {} }), +})); + jest.mock('../../common/hooks', () => ({ ...jest.requireActual('../../common/hooks'), useInstanceQuery: jest.fn().mockReturnValue({ instance: {}, isLoading: false }), diff --git a/src/Item/EditItem/EditItem.js b/src/Item/EditItem/EditItem.js index 43702cbc8..de57c9db8 100644 --- a/src/Item/EditItem/EditItem.js +++ b/src/Item/EditItem/EditItem.js @@ -17,13 +17,13 @@ import { useHolding, } from '../../common'; import ItemForm from '../../edit/items/ItemForm'; -import useCallout from '../../hooks/useCallout'; import { parseHttpError, switchAffiliation } from '../../utils'; import { useItem, useItemMutation, useBoundWithsMutation, } from '../hooks'; +import { useCallout, useConfigurationQuery } from '../../hooks'; const EditItem = ({ referenceData, @@ -39,6 +39,7 @@ const EditItem = ({ const { isLoading: isItemLoading, item } = useItem(itemId); const callout = useCallout(); const stripes = useStripes(); + const { configs } = useConfigurationQuery('number_generator'); const goBack = useCallback(() => { history.push({ @@ -114,6 +115,7 @@ const EditItem = ({ return ( <> jest.fn().mockReturnValue('ItemForm')); -jest.mock('../../hooks/useCallout', () => jest.fn().mockReturnValue({ sendCallout: jest.fn() })); +jest.mock('../../hooks', () => ({ + ...jest.requireActual('../../hooks'), + useCallout: () => jest.fn().mockReturnValue({ sendCallout: jest.fn() }), + useConfigurationQuery: () => ({ configs: {} }), +})); + jest.mock('../../common/hooks', () => ({ ...jest.requireActual('../../common/hooks'), useInstanceQuery: jest.fn().mockReturnValue({ instance: {}, isLoading: false }), diff --git a/src/edit/holdings/HoldingsForm.js b/src/edit/holdings/HoldingsForm.js index a48ad1c74..d48894e7b 100644 --- a/src/edit/holdings/HoldingsForm.js +++ b/src/edit/holdings/HoldingsForm.js @@ -33,6 +33,8 @@ import { ViewMetaData } from '@folio/stripes/smart-components'; import stripesFinalForm from '@folio/stripes/final-form'; +import { NumberGeneratorModalButton } from '@folio/service-interaction'; + import OptimisticLockingBanner from '../../components/OptimisticLockingBanner'; import ElectronicAccessFields from '../electronicAccessFields'; import { handleKeyCommand, validateOptionalField } from '../../utils'; @@ -115,6 +117,9 @@ class HoldingsForm extends React.Component { }), goTo: PropTypes.func.isRequired, httpError: PropTypes.object, + configs: PropTypes.shape({ + callNumberGeneratorSettingHoldings: PropTypes.string + }) }; static defaultProps = { @@ -189,6 +194,8 @@ class HoldingsForm extends React.Component { render() { const { + configs, + form: { change }, onCancel, initialValues, instance, @@ -225,6 +232,10 @@ class HoldingsForm extends React.Component { }), ) : []; + const { + callNumberGeneratorSettingHoldings, + } = configs; + const holdingsTypeOptions = referenceTables.holdingsTypes ? referenceTables.holdingsTypes.map( it => ({ label: it.name, @@ -538,15 +549,36 @@ class HoldingsForm extends React.Component { /> - } - name="callNumber" - id="additem_callnumber" - component={TextArea} - rows={1} - fullWidth - disabled={this.isFieldBlocked('callNumber')} - /> + + } + name="callNumber" + id="additem_callnumber" + component={TextArea} + rows={1} + fullWidth + disabled={this.isFieldBlocked('callNumber') || callNumberGeneratorSettingHoldings === 'useGenerator'} + /> + + + {( + callNumberGeneratorSettingHoldings === 'useGenerator' || + callNumberGeneratorSettingHoldings === 'useBoth' + ) && + + } + callback={(generated) => change('callNumber', generated)} + id="inventoryCallNumber" + generateButtonLabel={} + generator="inventory_callNumber" + modalProps={{ + label: + }} + /> + + } + ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../common', () => ({ LocationSelectionWithCheck: () =>
LocationSelection
, })); @@ -76,6 +80,7 @@ const HoldingsFormSetup = (props = {}) => ( > } name="barcode" id="additem_barcode" @@ -445,15 +459,18 @@ class ItemForm extends React.Component { autoFocus fullWidth /> + {renderBarcodeGenerator()} } name="accessionNumber" id="additem_accessionnumber" component={TextField} fullWidth /> + {renderAccessionNumberGenerator()} } name="itemLevelCallNumber" id="additem_callnumber" @@ -554,6 +572,7 @@ class ItemForm extends React.Component { rows={1} fullWidth /> + {renderCallNumberGenerator()} ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../common', () => ({ LocationSelectionWithCheck: () =>
LocationSelection
, })); @@ -63,6 +67,7 @@ const ItemFormSetup = (props = {}) => ( }} > { + const { + accessionNumberGeneratorSettingItems, + barcodeGeneratorSettingItems, + callNumberGeneratorSettingItems, + useAccessionNumberForCallNumberItems + } = configs; + + const accessionNumberGeneratorActive = accessionNumberGeneratorSettingItems === 'useGenerator' || + accessionNumberGeneratorSettingItems === 'useBoth'; + + const barcodeGeneratorActive = barcodeGeneratorSettingItems === 'useGenerator' || + barcodeGeneratorSettingItems === 'useBoth'; + + const callNumberGeneratorActive = callNumberGeneratorSettingItems === 'useGenerator' || + callNumberGeneratorSettingItems === 'useBoth'; + + const disableAccessionNumberField = accessionNumberGeneratorSettingItems === 'useGenerator'; + const disableBarcodeField = barcodeGeneratorSettingItems === 'useGenerator'; + const disableCallNumberField = callNumberGeneratorSettingItems === 'useGenerator'; + + // This is to ensure that if the field somehow _did_ get set when it's not supposed to, we ignore it + const useJointModal = useAccessionNumberForCallNumberItems && accessionNumberGeneratorActive && callNumberGeneratorActive; + + // Don't worry about calling logic in here, do that in renderAccession... and renderCall... + const renderJointNumberGenerator = () => ( + + + + } + callback={(generated) => { + change('accessionNumber', generated); + change('itemLevelCallNumber', generated); + }} + generateButtonLabel={} + generator="inventory_accessionNumber" + id="inventoryAccessionNumberAndCallNumber" + modalProps={{ + label: + }} + renderTop={() => ( + + + + )} + /> + ); + + const renderAccessionNumberGenerator = () => { + if (useJointModal) { + return renderJointNumberGenerator(); + } else if (accessionNumberGeneratorActive) { + return ( + } + callback={(generated) => change('accessionNumber', generated)} + generateButtonLabel={} + generator="inventory_accessionNumber" + id="inventoryAccessionNumber" + modalProps={{ + label: + }} + /> + ); + } + + return null; + }; + + const renderCallNumberGenerator = () => { + if (useJointModal) { + return renderJointNumberGenerator(); + } else if (callNumberGeneratorActive) { + return ( + } + callback={(generated) => change('itemLevelCallNumber', generated)} + generateButtonLabel={} + generator="inventory_callNumber" + id="inventoryCallNumber" + modalProps={{ + label: + }} + /> + ); + } + + return null; + }; + + const renderBarcodeGenerator = () => { + if (barcodeGeneratorActive) { + return ( + + } + callback={(generated) => change('barcode', generated)} + generateButtonLabel={} + generator="inventory_itemBarcode" + id="inventorybarcode" + modalProps={{ + label: + }} + /> + ); + } + return null; + }; + + return { + disableAccessionNumberField, + disableBarcodeField, + disableCallNumberField, + renderAccessionNumberGenerator, + renderCallNumberGenerator, + renderBarcodeGenerator + }; +}; + +export default getNumberGeneratorModals; diff --git a/src/hooks/index.js b/src/hooks/index.js index a4c19ae8e..89c8787cd 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -7,6 +7,7 @@ export { default as useHoldingsFromStorage } from './useHoldingsFromStorage'; export { default as useInstanceMutation } from './useInstanceMutation'; export { default as useHoldingsQueryByHrids } from './useHoldingsQueryByHrids'; export { default as useInventoryBrowse } from './useInventoryBrowse'; +export { default as useConfigurationQuery } from './useConfigurationQuery'; export { default as useLocationsQuery } from './useLocationsQuery'; export { default as useLastSearchTerms } from './useLastSearchTerms'; export { default as useSearchForShadowInstanceTenants } from './useSearchForShadowInstanceTenants'; diff --git a/src/hooks/useConfigurationQuery.js b/src/hooks/useConfigurationQuery.js new file mode 100644 index 000000000..b4d9da4ae --- /dev/null +++ b/src/hooks/useConfigurationQuery.js @@ -0,0 +1,33 @@ +import { useQuery } from 'react-query'; +import { useOkapiKy, useNamespace } from '@folio/stripes/core'; + +const useConfigurationQuery = (configName) => { + const ky = useOkapiKy().extend({ timeout: false }); + const [namespace] = useNamespace(); + const query = `query=(module=INVENTORY and configName=${configName})`; + + const queryKey = [namespace, query, 'useConfigurationQuery']; + + + const queryFn = () => ky.get(`configurations/entries?${query}`).json(); + const { data: { configs: { 0: settings = {} } = [], totalRecords } = {}, isLoading, isFetching } = useQuery({ queryKey, queryFn }); + + let parsedSettings = {}; + if (settings.value) { + try { + parsedSettings = JSON.parse(settings.value); + } catch (error) { + // Error parsing settings JSON + console.error(error); // eslint-disable-line no-console + } + } + + return { + isFetching, + isLoading, + configs: parsedSettings, + totalRecords, + }; +}; + +export default useConfigurationQuery; diff --git a/src/index.test.js b/src/index.test.js index 1a4f59666..6e0298972 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -6,6 +6,10 @@ import { EVENTS } from './constants'; jest.mock('./storage'); +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + const searchTermsExpectations = () => { expect(removeItem).toHaveBeenNthCalledWith(1, '@folio/inventory/search.instances.lastSearch'); expect(removeItem).toHaveBeenNthCalledWith(2, '@folio/inventory/search.holdings.lastSearch'); diff --git a/src/routes/CreateItemRoute.test.js b/src/routes/CreateItemRoute.test.js index 53c0e59d1..2d4ec0829 100644 --- a/src/routes/CreateItemRoute.test.js +++ b/src/routes/CreateItemRoute.test.js @@ -5,6 +5,10 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react import CreateItemRoute from './CreateItemRoute'; +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../Item', () => ({ ...jest.requireActual('../Item'), CreateItem: jest.fn().mockReturnValue('CreateItem') diff --git a/src/routes/DuplicateHoldingRoute.test.js b/src/routes/DuplicateHoldingRoute.test.js index 0994d43cc..de5107db5 100644 --- a/src/routes/DuplicateHoldingRoute.test.js +++ b/src/routes/DuplicateHoldingRoute.test.js @@ -5,6 +5,10 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react import DuplicateHoldingRoute from './DuplicateHoldingRoute'; +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../Holding', () => ({ ...jest.requireActual('../Holding'), DuplicateHolding: jest.fn().mockReturnValue('DuplicateHolding') diff --git a/src/routes/DuplicateItemRoute.test.js b/src/routes/DuplicateItemRoute.test.js index d604e25bb..0c4ef4b77 100644 --- a/src/routes/DuplicateItemRoute.test.js +++ b/src/routes/DuplicateItemRoute.test.js @@ -5,6 +5,10 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react import DuplicateItemRoute from './DuplicateItemRoute'; +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../Item', () => ({ ...jest.requireActual('../Item'), DuplicateItem: jest.fn().mockReturnValue('DuplicateItem') diff --git a/src/routes/EditHoldingRoute.test.js b/src/routes/EditHoldingRoute.test.js index 6b60c7080..b204da925 100644 --- a/src/routes/EditHoldingRoute.test.js +++ b/src/routes/EditHoldingRoute.test.js @@ -5,6 +5,10 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react import EditHoldingRoute from './EditHoldingRoute'; +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../Holding', () => ({ ...jest.requireActual('../Holding'), EditHolding: jest.fn().mockReturnValue('EditHolding') diff --git a/src/routes/EditItemRoute.test.js b/src/routes/EditItemRoute.test.js index 52a036da1..a6d16cf58 100644 --- a/src/routes/EditItemRoute.test.js +++ b/src/routes/EditItemRoute.test.js @@ -5,6 +5,10 @@ import { render, screen } from '@folio/jest-config-stripes/testing-library/react import EditItemRoute from './EditItemRoute'; +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
+})); + jest.mock('../Item', () => ({ ...jest.requireActual('../Item'), EditItem: jest.fn().mockReturnValue('EditItem') diff --git a/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.css b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.css new file mode 100644 index 000000000..6501c7205 --- /dev/null +++ b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.css @@ -0,0 +1,9 @@ +@import '@folio/stripes-components/lib/variables'; + +.marginBottomGutter { + margin-bottom: var(--gutter); +} + +.greyLabel { + color: var(--checkable-disabled-fill); +} \ No newline at end of file diff --git a/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.js b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.js new file mode 100644 index 000000000..3b6120897 --- /dev/null +++ b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.js @@ -0,0 +1,61 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { ConfigManager } from '@folio/stripes/smart-components'; + +import NumberGeneratorOptionsForm from './NumberGeneratorOptionsForm'; + +class NumberGeneratorOptions extends React.Component { + static propTypes = { + stripes: PropTypes.object, + }; + + constructor(props) { + super(props); + this.connectedConfigManager = stripesConnect(ConfigManager); + } + + defaultValues = { + barcodeGeneratorSetting: 'useTextField' + }; + + beforeSave(data) { + return JSON.stringify(data); + } + + getInitialValues = (settings) => { + let loadedValues = {}; + try { + const value = settings.length === 0 ? '' : settings[0].value; + loadedValues = JSON.parse(value); + } catch (e) { + // Make sure we return _something_ because ConfigManager no longer has a safety check here + return {}; + } + + return { + ...this.defaultValues, + ...loadedValues, + }; + } + + render() { + return ( + } + moduleName="INVENTORY" + onBeforeSave={this.beforeSave} + stripes={this.props.stripes} + formType="final-form" + > + + + ); + } +} + +export default withStripes(NumberGeneratorOptions); diff --git a/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.test.js b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.test.js new file mode 100644 index 000000000..a858e9866 --- /dev/null +++ b/src/settings/NumberGeneratorOptions/NumberGeneratorOptions.test.js @@ -0,0 +1,40 @@ +import React from 'react'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import stripesFinalForm from '@folio/stripes/final-form'; + +import '../../../test/jest/__mock__'; + +import { + renderWithIntl, + renderWithRouter, + translationsProperties +} from '../../../test/jest/helpers'; + +import NumberGeneratorOptions from './NumberGeneratorOptions'; + +const onSubmit = jest.fn(); + +const Form = ({ handleSubmit, ...props }) => ( +
+ + +); + +const WrappedForm = stripesFinalForm({ + navigationCheck: true, + enableReinitialize: false, +})(Form); + +const renderNumberGeneratorOptions = () => renderWithIntl( + renderWithRouter(), + translationsProperties, +); + +describe('Number generator settings', () => { + it('renders', () => { + renderNumberGeneratorOptions(); + + expect(screen.getByText('ConfigManager')).toBeInTheDocument(); + }); +}); diff --git a/src/settings/NumberGeneratorOptions/NumberGeneratorOptionsForm.js b/src/settings/NumberGeneratorOptions/NumberGeneratorOptionsForm.js new file mode 100644 index 000000000..0f21da223 --- /dev/null +++ b/src/settings/NumberGeneratorOptions/NumberGeneratorOptionsForm.js @@ -0,0 +1,261 @@ +import { useRef } from 'react'; + +import { FormattedMessage } from 'react-intl'; +import { Field, useFormState } from 'react-final-form'; + +import { + Accordion, + AccordionSet, + AccordionStatus, + Button, + Checkbox, + Col, + ExpandAllButton, + InfoPopover, + Label, + Layout, + MessageBanner, + RadioButton, + Row, +} from '@folio/stripes/components'; + +import css from './NumberGeneratorOptions.css'; + +const NumberGeneratorOptionsForm = () => { + const accordionStatusRef = useRef(); + const { values } = useFormState(); + const disableUseForBothFields = + (values?.accessionNumberGeneratorSettingItems ?? 'useTextField') === 'useTextField' || + (values?.callNumberGeneratorSettingItems ?? 'useTextField') === 'useTextField'; + + const disableAccessionNumberAndCallNumberOffOptions = !!values?.useAccessionNumberForCallNumberItems; + + return ( + <> + + +
+ + + +
+ +
+ + + + + + + + + + + } + id="number-generator-options-holdings" + > + + +
+ + + +
+ } + name="callNumberGeneratorSettingHoldings" + type="radio" + value="useTextField" + /> + } + name="callNumberGeneratorSettingHoldings" + type="radio" + value="useBoth" + /> + } + name="callNumberGeneratorSettingHoldings" + type="radio" + value="useGenerator" + /> + + +
+
+ + + + } + id="number-generator-options-items" + > + + +
+ + } + name="barcodeGeneratorSettingItems" + type="radio" + value="useTextField" + /> + } + name="barcodeGeneratorSettingItems" + type="radio" + value="useBoth" + /> + } + name="barcodeGeneratorSettingItems" + type="radio" + value="useGenerator" + /> +
+ +
+ + +
+ + + +
+ } + name="accessionNumberGeneratorSettingItems" + type="radio" + value="useTextField" + /> + } + name="accessionNumberGeneratorSettingItems" + type="radio" + value="useBoth" + /> + } + name="accessionNumberGeneratorSettingItems" + type="radio" + value="useGenerator" + /> + + +
+ + +
+ + + +
+ } + name="callNumberGeneratorSettingItems" + type="radio" + value="useTextField" + /> + } + name="callNumberGeneratorSettingItems" + type="radio" + value="useBoth" + /> + } + name="callNumberGeneratorSettingItems" + type="radio" + value="useGenerator" + /> + + +
+ + +
+ +
+ +
+ + + + }} + /> + + + } + iconSize="medium" + /> + + } + name="useAccessionNumberForCallNumberItems" + type="checkbox" + /> + {disableUseForBothFields && + + + + } +
+ +
+
+
+
+ + ); +}; + +export default NumberGeneratorOptionsForm; diff --git a/src/settings/NumberGeneratorOptions/index.js b/src/settings/NumberGeneratorOptions/index.js new file mode 100644 index 000000000..34e593541 --- /dev/null +++ b/src/settings/NumberGeneratorOptions/index.js @@ -0,0 +1 @@ +export { default } from './NumberGeneratorOptions'; diff --git a/src/settings/index.js b/src/settings/index.js index d37fc027b..1b50d01da 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -32,6 +32,7 @@ import ModesOfIssuanceSettings from './ModesOfIssuanceSettings'; import InstanceNoteTypesSettings from './InstanceNoteTypesSettings'; import NatureOfContentTermsSettings from './NatureOfContentTermsSettings'; import FastAddSettings from './FastAdd/FastAddSettings'; +import NumberGeneratorOptions from './NumberGeneratorOptions'; class InventorySettings extends React.Component { constructor(props) { @@ -199,6 +200,13 @@ class InventorySettings extends React.Component { component: CallNumberTypes, perm: this.addPerm('ui-inventory.settings.call-number-types'), }, + { + route: 'numbergeneratoroptions', + label: , + component: NumberGeneratorOptions, + interface: 'servint', + perm: 'ui-inventory.settings.numberGenerator.manage' + }, ] }, ]; diff --git a/test/jest/helpers/index.js b/test/jest/helpers/index.js index b9c939cf3..d30c8043c 100644 --- a/test/jest/helpers/index.js +++ b/test/jest/helpers/index.js @@ -2,3 +2,4 @@ export { default as stripesStub } from './stripesStub'; export { default as translationsProperties } from './translationsProperties'; export { default as renderWithFinalForm } from './renderWithFinalForm'; export { default as renderWithIntl } from './renderWithIntl'; +export { default as renderWithRouter } from './renderWithRouter'; diff --git a/translations/ui-inventory/en.json b/translations/ui-inventory/en.json index dbf8ecae5..2d43bbaf1 100644 --- a/translations/ui-inventory/en.json +++ b/translations/ui-inventory/en.json @@ -179,7 +179,31 @@ "selectTemporaryLocation": "Select temporary location", "selectLocation": "Select location", "selectAlternativeTitleType": "Select alternative title type", + "settings.learnMore": "Learn more", "settings.goBack": "Go back to inventory settings", + "settings.numberGeneratorOptions": "Number generator options", + "settings.numberGeneratorOptions.info": "Fields which are usually filled using a numeric sequence can use the number generator. When the generator is switched on the field can either be fixed to prevent manual update, or made fully editable. When switched off, the field must be filled manually.", + "settings.numberGeneratorOptions.useGeneratorForBarcode": "Number generator on, fixed: the barcode can be filled using the generator only.", + "settings.numberGeneratorOptions.useTextFieldForBarcode": "Number generator off: the barcode can be filled manually only.", + "settings.numberGeneratorOptions.useBothForBarcode": "Number generator on, editable: the barcode can be filled using the generator and be edited, or filled manually.", + "settings.numberGeneratorOptions.useGeneratorForAccessionNumber": "Number generator on, fixed: the accession number can be filled using the generator only.", + "settings.numberGeneratorOptions.useTextFieldForAccessionNumber": "Number generator off: the accession number can be filled manually only.", + "settings.numberGeneratorOptions.useBothForAccessionNumber": "Number generator on, editable: the accession number can be filled using the generator and be edited, or filled manually.", + "settings.numberGeneratorOptions.useAccessionNumberForCallNumber": "Use the same generated number for Accession number and Call number", + "settings.numberGeneratorOptions.useAccessionNumberForCallNumberWarning": "Warning: The checkbox has been disabled because the Accession number and/or Call number have been set to manual completion", + "settings.numberGeneratorOptions.useAccessionNumberForCallNumberInfo": "When this option is selected, the Accession and Call numbers in Inventory Item records will be generated using the same Accession Number sequence.{linebreak}{linebreak}This option is only available when the number generator is switched on in the above Accession number and Call number settings.", + "settings.numberGeneratorOptions.useGeneratorForCallNumber": "Number generator on, fixed: the call number can be filled using the generator only.", + "settings.numberGeneratorOptions.useTextFieldForCallNumber": "Number generator off: the call number can be filled manually only.", + "settings.numberGeneratorOptions.useBothForCallNumber": "Number generator on, editable: the call number can be filled using the generator and be edited, or filled manually.", + "numberGenerator.generateAccessionNumber": "Generate accession number", + "numberGenerator.generateCallNumber": "Generate call number", + "numberGenerator.generateAccessionAndCallNumber": "Generate accession and call numbers", + "numberGenerator.generateAccessionAndCallNumberWarning": "Both accession number and call number will be filled from this action, using a sequence from the Inventory: Accession number generator.", + "numberGenerator.generateItemBarcode": "Generate item barcode", + "numberGenerator.itemBarcodeGenerator": "Item barcode generator", + "numberGenerator.callNumberGenerator": "Call number generator", + "numberGenerator.accessionNumberGenerator": "Accession number generator", + "numberGenerator.accessionAndCallNumberGenerator": "Accession number and call number generator", "hridHandling": "HRID handling", "hridHandling.checkbox.label": "Remove leading zeroes from HRID", "hridHandling.description.line1": "After initial data migration, new FOLIO HRIDs are assigned sequentially, based on the starting number in these settings", @@ -190,7 +214,6 @@ "hridHandling.sectionHeader3": "Inventory item records", "hridHandling.label.startWith": "Start with", "hridHandling.label.assignPrefix": "Assign prefix", - "hridHandling.validation.enterValue": "Please enter a value", "hridHandling.validation.maxLength": "Invalid value. Maximum {maxLength} characters allowed", "hridHandling.validation.startWithField": "Please enter a numeric value", "hridHandling.validation.assignPrefixField": "Please enter an alphanumeric value",