From 6aa7b8bb12ddf1049f901c4e76d0b83460942a46 Mon Sep 17 00:00:00 2001 From: Oleksandr Hladchenko <85172747+OleksandrHladchenko1@users.noreply.github.com> Date: Wed, 15 May 2024 13:43:10 +0300 Subject: [PATCH] UIIN-2793: Populate Acquisitions accordion on instance when central ordering is active (#2482) * UIIN-2793: Populate Acquisitions accordion on instance when central ordering is active * Update package.json * UIIN-2793: Rename file for styles * UIIN-2793: Display sub accordions when user is in consortium mode AND the user is on non-central tenant * UIIN-2793: Fix tests * UIIN-2793: Changes after review * UIIN-2793: Fix tests * UIIN-2793: Change naming & mock useStripes * UIIN-2793: Update CHANGELOG.md * UIIN-2793: Fix typo in constants.js --- CHANGELOG.md | 1 + .../InstanceAcquisition.css | 3 + .../InstanceAcquisition.js | 106 +++++++++++------- .../InstanceAcquisition.test.js | 80 ++++++++++++- .../InstanceAcquisition/TenantAcquisition.js | 68 +++++++++++ .../TenantAcquisition.test.js | 30 +++++ .../InstanceAcquisition/fixtures.js | 1 + .../useInstanceAcquisition.js | 6 +- src/constants.js | 9 ++ test/jest/__mock__/stripesCore.mock.js | 2 +- 10 files changed, 256 insertions(+), 50 deletions(-) create mode 100644 src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.css create mode 100644 src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.js create mode 100644 src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8845c4274..5a9ec2c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * Jest/RTL: Cover ModalContent components with unit tests. Refs UIIN-2669. * Add callout noting user's active affiliation when it changes after selecting holding or item. Refs UIIN-2831, UIIN-2872. * Jest/RTL: Cover LocationSelectionWithCheck components with unit tests. Refs UIIN-2670. +* Populate Acquisitions accordion on instance when central ordering is active. Refs UIIN-2793. ## [11.0.4](https://github.com/folio-org/ui-inventory/tree/v11.0.4) (2024-04-30) [Full Changelog](https://github.com/folio-org/ui-inventory/compare/v11.0.3...v11.0.4) diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.css b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.css new file mode 100644 index 000000000..b450395ee --- /dev/null +++ b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.css @@ -0,0 +1,3 @@ +.tenantAcquisitionAccordion { + padding-left: 15px; +} diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.js b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.js index ab9da0d99..06ac02f24 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.js @@ -1,66 +1,90 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router-dom'; import { useStripes } from '@folio/stripes/core'; -import { - Accordion, - MultiColumnList, - NoValue, -} from '@folio/stripes/components'; +import { Accordion } from '@folio/stripes/components'; -import { getDateWithTime } from '../../../utils'; import { useControlledAccordion } from '../../../common'; +import { isUserInConsortiumMode } from '../../../utils'; import useInstanceAcquisition from './useInstanceAcquisition'; -const visibleColumns = ['poLineNumber', 'orderStatus', 'polReceiptStatus', 'dateOrdered', 'acqUnit', 'orderType']; -const columnMapping = { - poLineNumber: , - orderStatus: , - polReceiptStatus: , - dateOrdered: , - acqUnit: , - orderType: , -}; -const formatter = { - poLineNumber: i => {i.poLineNumber}, - orderStatus: i => ( - <> - {i.order?.workflowStatus ? : } - {i.order?.orderCloseReason?.reason && ` - ${i.order.orderCloseReason.reason}`} - - ), - polReceiptStatus: i => , - dateOrdered: i => getDateWithTime(i.order?.dateOrdered), - acqUnit: i => i.order?.acqUnits?.map(u => u.name)?.join(', ') || , - orderType: i => (i.order?.orderType ? : ), -}; +import TenantAcquisition from './TenantAcquisition'; + +import css from './InstanceAcquisition.css'; const InstanceAcquisition = ({ accordionId, instanceId }) => { const stripes = useStripes(); - const { isLoading, instanceAcquisition } = useInstanceAcquisition(instanceId); - const controlledAccorion = useControlledAccordion(Boolean(instanceAcquisition?.length)); + const activeTenant = stripes.okapi.tenant; + const centralTenant = stripes.user.user?.consortium?.centralTenantId; + + const { + isLoading: isLoadingActiveTenantAcquisition, + instanceAcquisition: activeTenantAcquisition, + } = useInstanceAcquisition(instanceId, activeTenant); + const { + isLoading: isLoadingCentralTenantAcquisition, + instanceAcquisition: centralTenantAcquisition, + } = useInstanceAcquisition(instanceId, centralTenant); + + const controlledAcqAccorion = useControlledAccordion(Boolean(activeTenantAcquisition?.length || centralTenantAcquisition?.length)); + const controlledActiveTenantAcqAccorion = useControlledAccordion(Boolean(activeTenantAcquisition?.length)); + const controlledCetralTenantAcqAccorion = useControlledAccordion(Boolean(centralTenantAcquisition?.length)); if (!(stripes.hasInterface('order-lines') && stripes.hasInterface('orders') && stripes.hasInterface('acquisitions-units'))) return null; + const renderTenantAcquisitionAccordion = (accId, tenantId, tenantAcquisitions, isLoading, controlledAccorionProps) => { + const getTenantAccordionLabel = (tenants, id) => tenants?.find(tenant => tenant.id === id).name; + + return ( + + + + ); + }; + return ( } - {...controlledAccorion} + {...controlledAcqAccorion} > - + {(isUserInConsortiumMode(stripes) && activeTenant !== centralTenant) ? ( + <> + {renderTenantAcquisitionAccordion( + 'active-acquisition-accordion', + activeTenant, + activeTenantAcquisition, + isLoadingActiveTenantAcquisition, + controlledActiveTenantAcqAccorion, + )} + {renderTenantAcquisitionAccordion( + 'central-acquisition-accordion', + centralTenant, + centralTenantAcquisition, + isLoadingCentralTenantAcquisition, + controlledCetralTenantAcqAccorion, + )} + + ) : ( + + ) + } ); }; diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js index dd0d570a9..c57343359 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js @@ -1,17 +1,25 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; +import { useStripes } from '@folio/stripes/core'; import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; -import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; +import { + renderWithIntl, + translationsProperties, +} from '../../../../test/jest/helpers'; import { resultData, line } from './fixtures'; import InstanceAcquisition from './InstanceAcquisition'; import useInstanceAcquisition from './useInstanceAcquisition'; +import * as utils from '../../../utils'; + jest.mock('./useInstanceAcquisition', () => jest.fn()); +const spyOnIsUserInConsortiumMode = jest.spyOn(utils, 'isUserInConsortiumMode'); + const renderInstanceAcquisition = (props = {}) => ( renderWithIntl( @@ -20,7 +28,8 @@ const renderInstanceAcquisition = (props = {}) => ( accordionId="accordionId" {...props} /> - + , + translationsProperties ) ); @@ -29,9 +38,70 @@ describe('InstanceAcquisition', () => { useInstanceAcquisition.mockClear().mockReturnValue({ instanceAcquisition: resultData }); }); - it('should display fetched instance acquisition data', () => { - renderInstanceAcquisition({ instanceId: 'instanceId' }); + describe('when user is non-consortial tenant', () => { + it('should display acquisition accordion and fetched instance acquisition data', () => { + spyOnIsUserInConsortiumMode.mockReturnValue(false); + useStripes.mockClear().mockReturnValue({ + hasInterface: () => true, + okapi: { tenant: 'diku' }, + user: { user: {} }, + }); + + const { container } = renderInstanceAcquisition({ instanceId: 'instanceId' }); + + expect(container.querySelector('#accordionId')).toBeInTheDocument(); + expect(screen.getByText(line.poLineNumber)).toBeInTheDocument(); + }); + }); + + describe('when user is in central tenant', () => { + it('should display acquisition accordion and fetched instance acquisition data', () => { + spyOnIsUserInConsortiumMode.mockReturnValue(true); + useStripes.mockClear().mockReturnValue({ + hasInterface: () => true, + okapi: { tenant: 'consortium' }, + user: { user: { + consortium: { centralTenantId: 'consortium' }, + tenants: [{ + id: 'consortium', + name: 'Consortium', + }], + } }, + }); + + const { container } = renderInstanceAcquisition({ instanceId: 'instanceId' }); + + expect(container.querySelector('#accordionId')).toBeInTheDocument(); + expect(screen.getByText(line.poLineNumber)).toBeInTheDocument(); + }); + }); + + describe('when user is in member tenant', () => { + it('should display central and member tenant subaccordions with fetched instance acquisition data', () => { + spyOnIsUserInConsortiumMode.mockReturnValue(true); + useStripes.mockClear().mockReturnValue({ + hasInterface: () => true, + okapi: { tenant: 'college' }, + user: { user: { + consortium: { centralTenantId: 'consortium' }, + tenants: [{ + id: 'consortium', + name: 'Consortium', + }, { + id: 'college', + name: 'College', + }], + } }, + }); + + const { container } = renderInstanceAcquisition({ instanceId: 'instanceId' }); + + expect(container.querySelector('#accordionId')).toBeInTheDocument(); + expect(container.querySelector('#active-acquisition-accordion')).toBeInTheDocument(); + expect(screen.getAllByText(line.poLineNumber)[0]).toBeInTheDocument(); - expect(screen.getByText(line.poLineNumber)).toBeInTheDocument(); + expect(container.querySelector('#central-acquisition-accordion')).toBeInTheDocument(); + expect(screen.getAllByText(line.poLineNumber)[1]).toBeInTheDocument(); + }); }); }); diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.js b/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.js new file mode 100644 index 000000000..b28de41f1 --- /dev/null +++ b/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.js @@ -0,0 +1,68 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { + MultiColumnList, + NoValue, +} from '@folio/stripes/components'; + +import { getDateWithTime } from '../../../utils'; +import { ACQUISITION_COLUMN_NAMES } from '../../../constants'; + +const visibleColumns = [ + ACQUISITION_COLUMN_NAMES.poLineNumber, + ACQUISITION_COLUMN_NAMES.orderStatus, + ACQUISITION_COLUMN_NAMES.polReceiptStatus, + ACQUISITION_COLUMN_NAMES.dateOrdered, + ACQUISITION_COLUMN_NAMES.acqUnit, + ACQUISITION_COLUMN_NAMES.orderType, +]; + +const columnMapping = { + [ACQUISITION_COLUMN_NAMES.poLineNumber]: , + [ACQUISITION_COLUMN_NAMES.orderStatus]: , + [ACQUISITION_COLUMN_NAMES.polReceiptStatus]: , + [ACQUISITION_COLUMN_NAMES.dateOrdered]: , + [ACQUISITION_COLUMN_NAMES.acqUnit]: , + [ACQUISITION_COLUMN_NAMES.orderType]: , +}; + +const formatter = { + [ACQUISITION_COLUMN_NAMES.poLineNumber]: i => {i.poLineNumber}, + [ACQUISITION_COLUMN_NAMES.orderStatus]: i => ( + <> + {i.order?.workflowStatus ? : } + {i.order?.orderCloseReason?.reason && ` - ${i.order.orderCloseReason.reason}`} + + ), + [ACQUISITION_COLUMN_NAMES.polReceiptStatus]: i => , + [ACQUISITION_COLUMN_NAMES.dateOrdered]: i => getDateWithTime(i.order?.dateOrdered), + [ACQUISITION_COLUMN_NAMES.acqUnit]: i => i.order?.acqUnits?.map(u => u.name)?.join(', ') || , + [ACQUISITION_COLUMN_NAMES.orderType]: i => (i.order?.orderType ? : ), +}; + +const TenantAcquisition = ({ + acquisitions, + isLoading, + tenantId, +}) => ( + +); + +TenantAcquisition.propTypes = { + acquisitions: PropTypes.arrayOf(PropTypes.object).isRequired, + isLoading: PropTypes.bool.isRequired, + tenantId: PropTypes.string.isRequired, +}; + +export default TenantAcquisition; diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.test.js b/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.test.js new file mode 100644 index 000000000..53005b5bd --- /dev/null +++ b/src/Instance/InstanceDetails/InstanceAcquisition/TenantAcquisition.test.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import { renderWithIntl } from '../../../../test/jest/helpers'; + +import { resultData, line } from './fixtures'; +import TenantAcquisition from './TenantAcquisition'; + +const renderTenantAcquisition = () => ( + renderWithIntl( + + + + ) +); + +describe('TenantAcquisition', () => { + it('should display instance acquisition data', () => { + renderTenantAcquisition(); + + expect(screen.getByText(line.poLineNumber)).toBeInTheDocument(); + }); +}); diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/fixtures.js b/src/Instance/InstanceDetails/InstanceAcquisition/fixtures.js index 626a29e23..88e69d1c0 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/fixtures.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/fixtures.js @@ -2,6 +2,7 @@ export const line = { id: 'lineId', purchaseOrderId: 'orderId', poLineNumber: '1000', + receiptStatus: 'Ongoing', }; export const order = { id: line.purchaseOrderId, diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.js b/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.js index 691bcecdb..b472ec587 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.js @@ -6,13 +6,13 @@ import { useOkapiKy, useNamespace, useStripes } from '@folio/stripes/core'; import { LIMIT_MAX } from '../../../constants'; import { batchRequest } from '../../../utils'; -const useInstanceAcquisition = (id) => { - const ky = useOkapiKy(); +const useInstanceAcquisition = (id, tenant) => { + const ky = useOkapiKy({ tenant }); const [namespace] = useNamespace({ key: 'instance-acquisition' }); const stripes = useStripes(); const { data = [], isLoading } = useQuery( - [namespace, 'instance-acquisition', id], + [namespace, 'instance-acquisition', id, tenant], async () => { const { titles = [] } = await ky.get('orders/titles', { searchParams: { diff --git a/src/constants.js b/src/constants.js index 1b4933481..05979f2b6 100644 --- a/src/constants.js +++ b/src/constants.js @@ -724,3 +724,12 @@ export const LEADER_RECORD_STATUSES = { }; export const USER_TOUCHED_STAFF_SUPPRESS_STORAGE_KEY = 'folio_user_touched_staff_suppress'; + +export const ACQUISITION_COLUMN_NAMES = { + poLineNumber: 'poLineNumber', + orderStatus: 'orderStatus', + polReceiptStatus: 'polReceiptStatus', + dateOrdered: 'dateOrdered', + acqUnit: 'acqUnit', + orderType: 'orderType', +}; diff --git a/test/jest/__mock__/stripesCore.mock.js b/test/jest/__mock__/stripesCore.mock.js index 25b8a169f..4f931063c 100644 --- a/test/jest/__mock__/stripesCore.mock.js +++ b/test/jest/__mock__/stripesCore.mock.js @@ -83,7 +83,7 @@ const mockStripesCore = { extend: jest.fn().mockReturnValue(this), }), - useStripes: () => STRIPES, + useStripes: jest.fn(() => STRIPES), withStripes: Component => ({ stripes, ...rest }) => { const fakeStripes = stripes || STRIPES;