diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f0d1d3..4f758c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Hide all actions except "Cancel Request" in Action menu (Lending library). Refs UIREQ-1032. * Hide all actions except "Cancel Request" in Action menu and DCB item links (Borrowing library). Refs UIREQ-1034. * Hide all actions except "Cancel Request" in Action menu and DCB item links (Pickup library). Refs UIREQ-1035. +* Request Action - Create new option Print search slips. Refs UIREQ-1039. * Hide Actions menu on closed request of DCB Transaction. Refs UIREQ-1040. ## [9.0.0](https://github.com/folio-org/ui-requests/tree/v9.0.0) (2023-10-12) diff --git a/package.json b/package.json index 5f69200b..73930b98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/requests", - "version": "9.0.0", + "version": "9.1.0", "description": "Requests manager", "repository": "folio-org/ui-requests", "publishConfig": { @@ -118,6 +118,7 @@ "circulation-storage.requests.item.post", "circulation-storage.request-preferences.collection.get", "circulation.pick-slips.get", + "circulation.search-slips.get", "inventory-storage.locations.item.get" ], "visible": true diff --git a/src/components/PrintContent/PrintContent.js b/src/components/PrintContent/PrintContent.js index 8768ba96..565bf9c3 100644 --- a/src/components/PrintContent/PrintContent.js +++ b/src/components/PrintContent/PrintContent.js @@ -7,12 +7,17 @@ import { buildTemplate } from '../../utils'; import css from './PrintContent.css'; -const PrintContent = forwardRef(({ dataSource, template }, ref) => { +const PrintContent = forwardRef(({ + dataSource, + template, + id, +}, ref) => { const templateFn = useMemo(() => buildTemplate(template), [template]); return (
@@ -33,8 +38,13 @@ const PrintContent = forwardRef(({ dataSource, template }, ref) => { }); PrintContent.propTypes = { + id: PropTypes.string, dataSource: PropTypes.arrayOf(PropTypes.object).isRequired, template: PropTypes.string.isRequired, }; +PrintContent.defaultProps = { + id: 'printContent', +}; + export default memo(PrintContent, isEqual); diff --git a/src/components/PrintContent/PrintContent.test.js b/src/components/PrintContent/PrintContent.test.js index d9a87577..0501a511 100644 --- a/src/components/PrintContent/PrintContent.test.js +++ b/src/components/PrintContent/PrintContent.test.js @@ -28,6 +28,7 @@ describe('PrintContent', () => { }, } ], + id: testIds.printContent, }; afterEach(() => { diff --git a/src/constants.js b/src/constants.js index a70da29c..a2fd4ddc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -250,7 +250,10 @@ export const requestLevelFilters = [ { label: 'ui-requests.filters.requestLevel.title', value: REQUEST_LEVEL_TYPES.TITLE }, ]; -export const pickSlipType = 'pick slip'; +export const SLIPS_TYPE = { + PICK_SLIP: 'Pick slip', + SEARCH_SLIP_HOLD_REQUESTS: 'Search slip (Hold requests)', +}; export const DOMAIN_NAME = 'requests'; diff --git a/src/routes/RequestsRoute.js b/src/routes/RequestsRoute.js index 3c7b7087..73e96b1f 100644 --- a/src/routes/RequestsRoute.js +++ b/src/routes/RequestsRoute.js @@ -48,7 +48,7 @@ import { reportHeaders, fulfillmentTypes, expiredHoldsReportHeaders, - pickSlipType, + SLIPS_TYPE, createModes, requestStatusesTranslations, requestTypesTranslations, @@ -93,6 +93,15 @@ const INITIAL_RESULT_COUNT = 30; const RESULT_COUNT_INCREMENT = 30; export const DEFAULT_FORMATTER_VALUE = ''; +export const getPrintHoldRequestsEnabled = (printHoldRequests) => { + const value = printHoldRequests.records[0]?.value; + const { + printHoldRequestsEnabled = false, + } = value ? JSON.parse(value) : {}; + + return printHoldRequestsEnabled; +}; + export const urls = { user: (value, idType) => { const query = stringify({ query: `(${idType}=="${value}")` }); @@ -392,9 +401,22 @@ class RequestsRoute extends React.Component { type: 'okapi', records: 'pickSlips', path: 'circulation/pick-slips/%{currentServicePoint.id}', - fetch: true, throwErrors: false, }, + searchSlips: { + type: 'okapi', + records: 'searchSlips', + path: 'circulation/search-slips/%{currentServicePoint.id}', + throwErrors: false, + }, + printHoldRequests: { + type: 'okapi', + records: 'configs', + path: 'configurations/entries', + params: { + query: '(module==SETTINGS and configName==PRINT_HOLD_REQUESTS)', + }, + }, currentServicePoint: {}, tags: { throwErrors: false, @@ -456,6 +478,9 @@ class RequestsRoute extends React.Component { pickSlips: PropTypes.shape({ GET: PropTypes.func, }).isRequired, + searchSlips: PropTypes.shape({ + GET: PropTypes.func, + }).isRequired, currentServicePoint: PropTypes.shape({ update: PropTypes.func.isRequired, }).isRequired, @@ -486,10 +511,17 @@ class RequestsRoute extends React.Component { records: PropTypes.arrayOf(PropTypes.object).isRequired, isPending: PropTypes.bool, }), + searchSlips: PropTypes.shape({ + records: PropTypes.arrayOf(PropTypes.object).isRequired, + isPending: PropTypes.bool, + }), configs: PropTypes.shape({ hasLoaded: PropTypes.bool.isRequired, records: PropTypes.arrayOf(PropTypes.object).isRequired, }), + printHoldRequests: PropTypes.shape({ + records: PropTypes.arrayOf(PropTypes.object).isRequired, + }), }).isRequired, stripes: PropTypes.shape({ connect: PropTypes.func.isRequired, @@ -571,7 +603,8 @@ class RequestsRoute extends React.Component { createTitleLevelRequestsByDefault, }; - this.printContentRef = React.createRef(); + this.pickSlipsPrintContentRef = React.createRef(); + this.searchSlipsPrintContentRef = React.createRef(); this.paneTitleRef = React.createRef(); } @@ -1037,10 +1070,12 @@ class RequestsRoute extends React.Component { this.setState({ errorModalData: {} }); }; - getPrintTemplate() { + getPrintTemplate(slipType) { const staffSlips = get(this.props.resources, 'staffSlips.records', []); - const pickSlip = staffSlips.find(slip => slip.name.toLowerCase() === pickSlipType); - return get(pickSlip, 'template', ''); + const slipTypeInLowerCase = slipType.toLowerCase(); + const slipTemplate = staffSlips.find(slip => slip.name.toLowerCase() === slipTypeInLowerCase); + + return get(slipTemplate, 'template', ''); } handleFilterChange = ({ name, values }) => { @@ -1097,6 +1132,16 @@ class RequestsRoute extends React.Component { ); }; + onBeforeGetContentForPrintButton = (onToggle) => ( + new Promise(resolve => { + this.context.sendCallout({ message: }); + onToggle(); + // without the timeout the printing process starts right away + // and the callout and onToggle above are blocked + setTimeout(() => resolve(), 1000); + }) + ); + render() { const { resources, @@ -1121,8 +1166,10 @@ class RequestsRoute extends React.Component { holdsShelfReportPending, createTitleLevelRequestsByDefault, } = this.state; + const isPrintHoldRequestsEnabled = getPrintHoldRequestsEnabled(resources.printHoldRequests); const { name: servicePointName } = this.getCurrentServicePointInfo(); const pickSlips = get(resources, 'pickSlips.records', []); + const searchSlips = get(resources, 'searchSlips.records', []); const patronGroups = get(resources, 'patronGroups.records', []); const addressTypes = get(resources, 'addressTypes.records', []); const servicePoints = get(resources, 'servicePoints.records', []); @@ -1135,11 +1182,15 @@ class RequestsRoute extends React.Component { createTitleLevelRequest: createTitleLevelRequestsByDefault, }; - const pickSlipsArePending = resources?.pickSlips?.isPending; + const isPickSlipsArePending = resources?.pickSlips?.isPending; + const isSearchSlipsArePending = resources?.searchSlips?.isPending; const requestsEmpty = isEmpty(requests); - const pickSlipsEmpty = isEmpty(pickSlips); - const printTemplate = this.getPrintTemplate(); - const pickSlipsData = convertToSlipData(pickSlips, intl, timezone, locale); + const isPickSlipsEmpty = isEmpty(pickSlips); + const isSearchSlipsEmpty = isEmpty(searchSlips); + const pickSlipsPrintTemplate = this.getPrintTemplate(SLIPS_TYPE.PICK_SLIP); + const searchSlipsPrintTemplate = this.getPrintTemplate(SLIPS_TYPE.SEARCH_SLIP_HOLD_REQUESTS); + const pickSlipsData = convertToSlipData(pickSlips, intl, timezone, locale, SLIPS_TYPE.PICK_SLIP); + const searchSlipsData = convertToSlipData(searchSlips, intl, timezone, locale, SLIPS_TYPE.SEARCH_SLIP_HOLD_REQUESTS); const resultsFormatter = getListFormatter(this.getRowURL, this.setURL); const actionMenu = ({ onToggle, renderColumnsMenu }) => ( @@ -1173,7 +1224,7 @@ class RequestsRoute extends React.Component { } { - pickSlipsArePending ? + isPickSlipsArePending ? : @@ -1196,18 +1247,10 @@ class RequestsRoute extends React.Component { new Promise(resolve => { - this.context.sendCallout({ message: }); - onToggle(); - // without the timeout the printing process starts right away - // and the callout and onToggle above are blocked - setTimeout(() => resolve(), 1000); - }) - } + disabled={isPickSlipsEmpty} + template={pickSlipsPrintTemplate} + contentRef={this.pickSlipsPrintContentRef} + onBeforeGetContent={() => this.onBeforeGetContentForPrintButton(onToggle)} > } + { + isPrintHoldRequestsEnabled && + <> + { + isSearchSlipsArePending ? + + + : + this.onBeforeGetContentForPrintButton(onToggle)} + > + + + } + + } {renderColumnsMenu} @@ -1314,10 +1381,20 @@ class RequestsRoute extends React.Component { />
+ { + isPrintHoldRequestsEnabled && + + } ); diff --git a/src/routes/RequestsRoute.test.js b/src/routes/RequestsRoute.test.js index 79457819..5871d5ad 100644 --- a/src/routes/RequestsRoute.test.js +++ b/src/routes/RequestsRoute.test.js @@ -32,6 +32,7 @@ import RequestsRoute, { buildHoldRecords, getRequestErrorMessage, getListFormatter, + getPrintHoldRequestsEnabled, urls, REQUEST_ERROR_MESSAGE_CODE, REQUEST_ERROR_MESSAGE_TRANSLATION_KEYS, @@ -117,7 +118,7 @@ jest.mock('../components', () => ({ {children}
)), - PrintContent: jest.fn(() =>
PrintContent
) + PrintContent: jest.fn(({ printContentTestId }) =>
PrintContent
) })); jest.mock('../components/RequestsFilters/RequestsFilters', () => ({ onClear }) => { return ( @@ -146,6 +147,8 @@ global.fetch = jest.fn(() => Promise.resolve({ const testIds = { searchAndSort: 'searchAndSort', + pickSlipsPrintTemplate: 'pickSlipsPrintTemplate', + searchSlipsPrintTemplate: 'searchSlipsPrintTemplate', }; const RequestFilterData = { onChange: jest.fn(), @@ -159,6 +162,7 @@ const labelIds = { closedCancelledRequest: requestStatusesTranslations[requestStatuses.CANCELLED], requestType: requestTypesTranslations[requestTypesMap.RECALL], printPickSlips: 'ui-requests.printPickSlips', + printSearchSlips: 'ui-requests.printSearchSlips', }; SearchAndSort.mockImplementation(jest.fn(({ @@ -321,6 +325,9 @@ describe('RequestsRoute', () => { pickSlips: { GET: jest.fn(), }, + searchSlips: { + GET: jest.fn(), + }, proxy: { reset: jest.fn(), GET: jest.fn(), @@ -375,6 +382,11 @@ describe('RequestsRoute', () => { } ], }, + printHoldRequests: { + records: [{ + value: '{"printHoldRequestsEnabled": true}', + }], + }, currentServicePoint: {}, patronBlocks: { records: [ @@ -390,6 +402,13 @@ describe('RequestsRoute', () => { } ], }, + searchSlips: { + records: [ + { + name: 'search slip', + } + ], + }, query: { filters: 'filter1.value1,filter1.value2,filter2.value3', instanceId: 'instanceId', @@ -474,8 +493,14 @@ describe('RequestsRoute', () => { expect(RequestFilterData.onChange).toBeCalled(); }); - it('should render PrintContent', () => { - const printContent = screen.getByText('PrintContent'); + it('should render PrintContent for pick slips', () => { + const printContent = screen.getByTestId(testIds.pickSlipsPrintTemplate); + + expect(printContent).toBeInTheDocument(); + }); + + it('should render PrintContent for search slips', () => { + const printContent = screen.getByTestId(testIds.searchSlipsPrintTemplate); expect(printContent).toBeInTheDocument(); }); @@ -534,10 +559,18 @@ describe('RequestsRoute', () => { it('should render print pick slips label', async () => { const printPickSlipsLabel = screen.getByText(labelIds.printPickSlips); - await userEvent.click(screen.getByRole('button', { name: 'onBeforeGetContent' })); + await userEvent.click(screen.getAllByRole('button', { name: 'onBeforeGetContent' })[0]); expect(printPickSlipsLabel).toBeInTheDocument(); }); + + it('should render print search slips label', async () => { + const printSearchSlipsLabel = screen.getByText(labelIds.printSearchSlips); + + await userEvent.click(screen.getAllByRole('button', { name: 'onBeforeGetContent' })[1]); + + expect(printSearchSlipsLabel).toBeInTheDocument(); + }); }); describe('focus event', () => { @@ -936,6 +969,30 @@ describe('RequestsRoute', () => { }); }); + describe('getPrintHoldRequestsEnabled', () => { + it('should return true when printHoldRequestsEnabled is true', () => { + expect(getPrintHoldRequestsEnabled({ + records: [{ + value: '{"printHoldRequestsEnabled": true}', + }], + })).toBeTruthy(); + }); + + it('should return false when printHoldRequestsEnabled is false', () => { + expect(getPrintHoldRequestsEnabled({ + records: [{ + value: '{"printHoldRequestsEnabled": false}', + }], + })).toBeFalsy(); + }); + + it('should return false when value absent', () => { + expect(getPrintHoldRequestsEnabled({ + records: [], + })).toBeFalsy(); + }); + }); + describe('urls', () => { const mockedQueryValue = 'testQuery'; const idType = 'idType'; diff --git a/src/utils.js b/src/utils.js index 14e32236..25512718 100644 --- a/src/utils.js +++ b/src/utils.js @@ -34,6 +34,7 @@ import { DCB_INSTANCE_ID, DCB_HOLDINGS_RECORD_ID, DCB_USER, + SLIPS_TYPE, } from './constants'; import css from './requests.css'; @@ -178,7 +179,7 @@ export function buildLocaleDateAndTime(dateTime, timezone, locale) { .format('L LT'); } -export const convertToSlipData = (source, intl, timeZone, locale, slipName = 'Pick slip') => { +export const convertToSlipData = (source, intl, timeZone, locale, slipName = SLIPS_TYPE.PICK_SLIP) => { return source.map(pickSlip => { const { item = {}, diff --git a/translations/ui-requests/en.json b/translations/ui-requests/en.json index 0dc5bd71..4084618e 100644 --- a/translations/ui-requests/en.json +++ b/translations/ui-requests/en.json @@ -266,6 +266,7 @@ "cancellationReason": "Cancellation reason", "cancellationAdditionalInformation": "Additional information for patron", "printPickSlips": "Print pick slips for {sp}", + "printSearchSlips": "Print search slips for {sp}", "common.cancel": "Cancel", "common.saveAndClose": "Save & close", @@ -293,6 +294,7 @@ "permission.create": "Requests: View, create", "permission.edit": "Requests: View, edit, cancel", "pickSlipsLoading": "Pick slips are loading, please wait", + "searchSlipsLoading": "Search slips are loading, please wait", "printInProgress": "Print options loading in progress. It might take a few seconds, please be patient.", "csvReportPending": "CSV report is being generated. Please wait.", "csvReportInProgress": "A CSV results report is being generated. This may take some time for larger result sets. Please be patient.",