diff --git a/package.json b/package.json index d5d9377c..25b15d4f 100644 --- a/package.json +++ b/package.json @@ -293,6 +293,14 @@ "organizations-storage.banking-account-types.item.put", "organizations-storage.banking-account-types.item.delete" ] + }, + { + "permissionName": "ui-organizations.settings.numberGenerator.manage", + "displayName": "Settings (Organizations): Manage number generator options", + "subPermissions": [ + "settings.organizations.enabled" + ], + "visible": true } ] }, @@ -323,6 +331,7 @@ "@bigtest/react": "^0.1.2", "@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", "@formatjs/cli": "^6.1.3", @@ -362,6 +371,7 @@ "redux-form": "^8.3.0" }, "peerDependencies": { + "@folio/service-interaction": "^3.0.0", "@folio/stripes": "^9.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -374,5 +384,8 @@ "optionalDependencies": { "@folio/plugin-find-contact": "^5.0.0", "@folio/plugin-find-interface": "^5.0.0" + }, + "optionalOkapiInterfaces": { + "servint": "2.0 3.0" } } diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js index 201e31e1..960f46df 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/FieldCode/FieldCode.js @@ -1,13 +1,17 @@ import React, { useCallback } from 'react'; -import { Field } from 'react-final-form'; +import { Field, useForm } from 'react-final-form'; import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import { stripesConnect } from '@folio/stripes/core'; -import { TextField } from '@folio/stripes/components'; +import { stripesConnect, useStripes } from '@folio/stripes/core'; +import { Col, Row, TextField } from '@folio/stripes/components'; +import { NumberGeneratorModalButton } from '@folio/service-interaction'; import { fetchOrgsByParam } from '../../../../common/resources'; import { validateOrgCode } from './validateOrgCode'; +import { useSettings } from '../../../../common/hooks'; + +const CONFIG_NAME = 'number_generator'; const FieldCode = ({ orgId, mutator }) => { const validate = useCallback(value => { @@ -16,15 +20,47 @@ const FieldCode = ({ orgId, mutator }) => { // eslint-disable-next-line react-hooks/exhaustive-deps [orgId]); + const { change } = useForm(); + const stripes = useStripes(); + + const { settings } = useSettings([CONFIG_NAME]); + let vendorCodeSetting = 'useTextField'; + + if (stripes.hasInterface('servint')) { + vendorCodeSetting = settings?.find(sett => sett?.configName === CONFIG_NAME)?.parsedSettings?.vendorGeneratorSetting ?? 'useTextField'; + } + return ( - } - name="code" - required - validate={validate} - /> + + + } + name="code" + required + validate={validate} + /> + + {( + vendorCodeSetting === 'useGenerator' || + vendorCodeSetting === 'useBoth' + ) && + + } + callback={(generated) => change('code', generated)} + id="vendor-code-generator" + generateButtonLabel={} + generator="organizations_vendorCode" + modalProps={{ + label: + }} + /> + + } + ); }; diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js index 15e5b133..d727575d 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/OrganizationSummaryForm.test.js @@ -11,6 +11,19 @@ import { useTypes } from '../../../common/hooks'; jest.mock('../../../common/hooks', () => ({ useTypes: jest.fn(), + useSettings: jest.fn(() => ([ + { + id: 'f2b84177-d85a-4b0c-93dd-279e8f9d1d42', + module: 'ORGANIZATIONS', + configName: 'number_generator', + enabled: true, + value: '{"vendorGeneratorSetting":"useBoth"}', + }, + ])), +})); + +jest.mock('@folio/service-interaction', () => ({ + NumberGeneratorModalButton: () =>
NumberGeneratorModalButton
})); const TestForm = stripesFinalForm({})( diff --git a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap index 1e46016a..a2bd9669 100644 --- a/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap +++ b/src/Organizations/OrganizationForm/OrganizationSummaryForm/__snapshots__/OrganizationSummaryForm.test.js.snap @@ -52,42 +52,50 @@ exports[`OrganizationSummaryForm should render correct structure 1`] = ` class="col-xs-6 col-md-3" >
-
- +
+ +
+ +
+
+
-
{ + const beforeSave = (data) => { + return JSON.stringify(data); + }; + + const 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 { + ...loadedValues, + }; + }; + + return ( + } + moduleName="ORGANIZATIONS" + onBeforeSave={beforeSave} + stripes={props.stripes} + > + + +
+ + + +
+ +
+ + + } + type="radio" + value="useTextField" + /> + } + type="radio" + value="useBoth" + /> + } + type="radio" + value="useGenerator" + /> + + +
+ ); +}; + +NumberGeneratorOptions.propTypes = { + stripes: PropTypes.object, +}; + +export default withStripes(NumberGeneratorOptions); diff --git a/src/Settings/SettingsPage.js b/src/Settings/SettingsPage.js index 5dd8ba63..24ac0117 100644 --- a/src/Settings/SettingsPage.js +++ b/src/Settings/SettingsPage.js @@ -1,4 +1,5 @@ -import { useMemo } from 'react'; +import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { Settings } from '@folio/stripes/smart-components'; @@ -6,6 +7,7 @@ import { Settings } from '@folio/stripes/smart-components'; import { useBankingInformationSettings } from '../common/hooks'; import { CategorySettings } from './CategorySettings'; import { TypeSettings } from './TypeSettings'; +import NumberGeneratorOptions from './NumberGeneratorOptions'; import { BankingAccountTypeSettings } from './BankingAccountTypeSettings'; import { BankingInformationSettings } from './BankingInformationSettings'; @@ -22,6 +24,13 @@ const pages = [ perm: 'settings.organizations.enabled', route: 'type', }, + { + component: NumberGeneratorOptions, + label: , + perm: 'ui-organizations.settings.numberGenerator.manage', + interface: 'servint', + route: 'numberGeneratorOptions', + }, { component: BankingInformationSettings, label: , @@ -52,4 +61,15 @@ const SettingsPage = (props) => { ); }; +SettingsPage.propTypes = { + stripes: PropTypes.shape({ + hasInterface: PropTypes.func.isRequired, + timezone: PropTypes.string.isRequired, + store: PropTypes.shape({ + dispatch: PropTypes.func.isRequired, + getState: PropTypes.func, + }), + }).isRequired, +}; + export default SettingsPage; diff --git a/src/common/constants/api.js b/src/common/constants/api.js index 75237d36..5d3d53ee 100644 --- a/src/common/constants/api.js +++ b/src/common/constants/api.js @@ -8,4 +8,6 @@ export const PRIVILEGED_CONTACTS_API = 'organizations-storage/privileged-contact export const SETTINGS_API = 'organizations-storage/settings'; export const TYPES_API = 'organizations-storage/organization-types'; +export const CONFIG_API = 'configurations/entries'; + export const MAX_LIMIT = 2147483647; diff --git a/src/common/hooks/index.js b/src/common/hooks/index.js index 9b6c0fd4..45d16b23 100644 --- a/src/common/hooks/index.js +++ b/src/common/hooks/index.js @@ -10,3 +10,4 @@ export * from './useLinkedAgreements'; export * from './useOrganizationBankingInformation'; export * from './useTranslatedCategories'; export * from './useTypes'; +export * from './useSettings'; diff --git a/src/common/hooks/useSettings/index.js b/src/common/hooks/useSettings/index.js new file mode 100644 index 00000000..3deefacf --- /dev/null +++ b/src/common/hooks/useSettings/index.js @@ -0,0 +1 @@ +export { useSettings } from './useSettings'; diff --git a/src/common/hooks/useSettings/useSettings.js b/src/common/hooks/useSettings/useSettings.js new file mode 100644 index 00000000..7cc05169 --- /dev/null +++ b/src/common/hooks/useSettings/useSettings.js @@ -0,0 +1,61 @@ +import { useQuery } from 'react-query'; + +import { + useNamespace, + useOkapiKy, +} from '@folio/stripes/core'; + +import { CONFIG_API } from '../../constants'; + +export const useSettings = (configNames = []) => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'settings' }); + + let query = 'module==ORGANIZATIONS'; + + if (configNames?.length > 0) { + const configNamesString = configNames?.map(cfn => `configName==${cfn}`).join(' or '); + + query = `${query} AND (${configNamesString})`; + } + + const searchParams = { + query, + }; + + const queryKeys = [ + namespace, + ]; + + const queryFn = () => ky.get(CONFIG_API, { searchParams }).json(); + const options = { + enabled: configNames?.length > 0, + keepPreviousData: true, + }; + + const { + data, + isFetching, + isLoading, + ...rest + } = useQuery( + queryKeys, + queryFn, + options, + ); + + const settings = data?.configs ?? []; + + const parsedSettings = settings?.map(sett => ({ + ...sett, + parsedSettings: JSON.parse(sett?.value ?? '{}'), + })); + + return ({ + settings: parsedSettings, + isFetching, + isLoading, + totalCount: data?.totalRecords, + ...rest, + }); +}; diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json index 4dd169c5..02e50d25 100644 --- a/translations/ui-organizations/en.json +++ b/translations/ui-organizations/en.json @@ -455,6 +455,13 @@ "settings.type": "Type", "settings.typeStatus.Active": "Active", "settings.typeStatus.Inactive": "Inactive", + "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.useGeneratorForVendor": "Number generator on, fixed: the vendor code can be filled using the generator only.", + "settings.numberGeneratorOptions.useTextFieldForVendor": "Number generator off: the vendor code can be filled manually only.", + "settings.numberGeneratorOptions.useBothForVendor": "Number generator on, editable: the vendor code can be filled using the generator and be edited, or filled manually.", + "numberGenerator.vendorCodeGenerator": "Vendor code generator", + "numberGenerator.generateVendorCode": "Generate vendor code", "settings.bankingInformation": "Banking information", "settings.bankingInformation.enable": "Enable banking information", "settings.bankingAccountTypes": "Account types",