diff --git a/lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js b/lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js
index ba6b08e9..61389f57 100644
--- a/lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js
+++ b/lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js
@@ -95,12 +95,18 @@ export const useRecordsSelect = ({ records }) => {
[selectedRecordsMap],
);
+ const resetSelectedRecords = useCallback(() => {
+ setSelectedRecordsMap({});
+ emitChangeEvent({});
+ }, [emitChangeEvent]);
+
return {
allRecordsSelected,
- selectedRecordsMap,
+ isRecordSelected,
+ resetSelectedRecords,
selectedRecordsLength,
- toggleSelectAll,
+ selectedRecordsMap,
selectRecord,
- isRecordSelected,
+ toggleSelectAll,
};
};
diff --git a/lib/claiming/components/index.js b/lib/claiming/components/index.js
index 7c64335c..23938172 100644
--- a/lib/claiming/components/index.js
+++ b/lib/claiming/components/index.js
@@ -1 +1,2 @@
export * from './menu-items';
+export * from './modals';
diff --git a/lib/claiming/components/modals/DelayClaimsModal/DelayClaimsModal.js b/lib/claiming/components/modals/DelayClaimsModal/DelayClaimsModal.js
new file mode 100644
index 00000000..26e1536b
--- /dev/null
+++ b/lib/claiming/components/modals/DelayClaimsModal/DelayClaimsModal.js
@@ -0,0 +1,85 @@
+import PropTypes from 'prop-types';
+import {
+ FormattedMessage,
+ useIntl,
+} from 'react-intl';
+
+import {
+ Button,
+ Col,
+ Modal,
+ Row,
+} from '@folio/stripes/components';
+import stripesFinalForm from '@folio/stripes/final-form';
+
+import { ModalFooter } from '../../../../ModalFooter';
+import { FieldClaimingDate } from '../../FieldClaimingDate';
+
+const DelayClaimsModal = ({
+ claimsCount,
+ onCancel,
+ handleSubmit,
+ open,
+}) => {
+ const intl = useIntl();
+ const modalLabel = intl.formatMessage(
+ { id: 'stripes-acq-components.claiming.modal.delayClaim.heading' },
+ { count: claimsCount },
+ );
+
+ const start = (
+
+ );
+ const end = (
+
+ );
+
+ const footer = (
+
+ );
+
+ return (
+
+
+
+ );
+};
+
+DelayClaimsModal.propTypes = {
+ claimsCount: PropTypes.number,
+ handleSubmit: PropTypes.func.isRequired,
+ onCancel: PropTypes.func.isRequired,
+ open: PropTypes.bool,
+};
+
+export default stripesFinalForm({
+ navigationCheck: true,
+ subscription: { values: true },
+})(DelayClaimsModal);
diff --git a/lib/claiming/components/modals/DelayClaimsModal/index.js b/lib/claiming/components/modals/DelayClaimsModal/index.js
new file mode 100644
index 00000000..c2b2080c
--- /dev/null
+++ b/lib/claiming/components/modals/DelayClaimsModal/index.js
@@ -0,0 +1 @@
+export { default as DelayClaimsModal } from './DelayClaimsModal';
diff --git a/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.js b/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.js
new file mode 100644
index 00000000..956ce92e
--- /dev/null
+++ b/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.js
@@ -0,0 +1,118 @@
+import identity from 'lodash/identity';
+import PropTypes from 'prop-types';
+import { Field } from 'react-final-form';
+import {
+ FormattedMessage,
+ useIntl,
+} from 'react-intl';
+
+import {
+ Button,
+ Col,
+ Modal,
+ Row,
+ TextArea,
+} from '@folio/stripes/components';
+import stripesFinalForm from '@folio/stripes/final-form';
+
+import { ModalFooter } from '../../../../ModalFooter';
+import { FieldClaimingDate } from '../../FieldClaimingDate';
+
+const SendClaimsModal = ({
+ claimsCount,
+ handleSubmit,
+ onCancel,
+ open,
+ message,
+}) => {
+ const intl = useIntl();
+ const modalLabel = intl.formatMessage(
+ { id: 'stripes-acq-components.claiming.modal.sendClaim.heading' },
+ { count: claimsCount },
+ );
+
+ const start = (
+
+ );
+ const end = (
+
+ );
+
+ const footer = (
+
+ );
+
+ return (
+
+
+
+ );
+};
+
+SendClaimsModal.propTypes = {
+ claimsCount: PropTypes.number,
+ handleSubmit: PropTypes.func.isRequired,
+ onCancel: PropTypes.func.isRequired,
+ open: PropTypes.bool,
+ message: PropTypes.node,
+};
+
+export default stripesFinalForm({
+ navigationCheck: true,
+ subscription: { values: true },
+})(SendClaimsModal);
diff --git a/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.test.js b/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.test.js
new file mode 100644
index 00000000..db75c474
--- /dev/null
+++ b/lib/claiming/components/modals/SendClaimsModal/SendClaimsModal.test.js
@@ -0,0 +1,81 @@
+import { render, screen } from '@testing-library/react';
+import user from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+
+import { dayjs } from '@folio/stripes/components';
+
+import SendClaimsModal from './SendClaimsModal';
+
+const FORMAT = 'MM/DD/YYYY';
+const today = dayjs();
+
+const defaultProps = {
+ onCancel: jest.fn(),
+ onSubmit: jest.fn(),
+ open: true,
+};
+
+const renderSendClaimsModal = (props = {}) => render(
+ ,
+ { wrapper: MemoryRouter },
+);
+
+describe('SendClaimsModal', () => {
+ beforeEach(() => {
+ defaultProps.onCancel.mockClear();
+ defaultProps.onSubmit.mockClear();
+ });
+
+ it('should render send claim modal', () => {
+ renderSendClaimsModal();
+
+ expect(screen.getByText('ui-receiving.modal.sendClaim.heading')).toBeInTheDocument();
+ });
+
+ it('should validate "Claim expiry date" field', async () => {
+ renderSendClaimsModal();
+
+ const saveBtn = screen.getByRole('button', { name: 'stripes-acq-components.FormFooter.save' });
+
+ await user.click(saveBtn);
+ expect(screen.getByText('stripes-acq-components.validation.required')).toBeInTheDocument();
+
+ await user.type(screen.getByPlaceholderText(FORMAT), today.format(FORMAT));
+ await user.click(saveBtn);
+ expect(screen.getByText('ui-receiving.validation.dateAfter')).toBeInTheDocument();
+ });
+
+ it('should submit valid form', async () => {
+ renderSendClaimsModal();
+
+ const date = today.add(5, 'days');
+ const internalNote = 'Internal';
+ const externalNote = 'External';
+
+ await user.type(screen.getByPlaceholderText(FORMAT), date.format(FORMAT));
+ await user.type(screen.getByLabelText('ui-receiving.piece.internalNote'), internalNote);
+ await user.type(screen.getByLabelText('ui-receiving.piece.externalNote'), externalNote);
+ await user.click(screen.getByRole('button', { name: 'stripes-acq-components.FormFooter.save' }));
+
+ expect(defaultProps.onSubmit).toHaveBeenCalledWith(
+ {
+ claimingDate: date.format('YYYY-MM-DD'),
+ internalNote,
+ externalNote,
+ },
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+
+ it('should call "onCancel" when the modal dismissed', async () => {
+ renderSendClaimsModal();
+
+ await user.click(screen.getByRole('button', { name: 'stripes-acq-components.FormFooter.cancel' }));
+
+ expect(defaultProps.onCancel).toHaveBeenCalled();
+ });
+});
diff --git a/lib/claiming/components/modals/SendClaimsModal/index.js b/lib/claiming/components/modals/SendClaimsModal/index.js
new file mode 100644
index 00000000..60f3fa57
--- /dev/null
+++ b/lib/claiming/components/modals/SendClaimsModal/index.js
@@ -0,0 +1 @@
+export { default as SendClaimsModal } from './SendClaimsModal';
diff --git a/lib/claiming/components/modals/index.js b/lib/claiming/components/modals/index.js
new file mode 100644
index 00000000..d0304545
--- /dev/null
+++ b/lib/claiming/components/modals/index.js
@@ -0,0 +1,2 @@
+export { DelayClaimsModal } from './DelayClaimsModal';
+export { SendClaimsModal } from './SendClaimsModal';
diff --git a/lib/claiming/hooks/index.js b/lib/claiming/hooks/index.js
index d9d4766b..43509495 100644
--- a/lib/claiming/hooks/index.js
+++ b/lib/claiming/hooks/index.js
@@ -1 +1,2 @@
+export { useClaimsDelay } from './useClaimsDelay';
export { useClaimsSend } from './useClaimsSend';
diff --git a/lib/claiming/hooks/useClaimsDelay/index.js b/lib/claiming/hooks/useClaimsDelay/index.js
new file mode 100644
index 00000000..0be7a569
--- /dev/null
+++ b/lib/claiming/hooks/useClaimsDelay/index.js
@@ -0,0 +1 @@
+export { useClaimsDelay } from './useClaimsDelay';
diff --git a/lib/claiming/hooks/useClaimsDelay/useClaimsDelay.js b/lib/claiming/hooks/useClaimsDelay/useClaimsDelay.js
new file mode 100644
index 00000000..864546c2
--- /dev/null
+++ b/lib/claiming/hooks/useClaimsDelay/useClaimsDelay.js
@@ -0,0 +1,26 @@
+import { useCallback } from 'react';
+
+import { PIECE_STATUS } from '../../../constants';
+import { usePiecesStatusBatchUpdate } from '../../../hooks';
+
+export const useClaimsDelay = () => {
+ const {
+ isLoading,
+ updatePiecesStatus,
+ } = usePiecesStatusBatchUpdate();
+
+ const delayClaims = useCallback(({ claimingInterval, pieceIds }) => {
+ return updatePiecesStatus({
+ data: {
+ claimingInterval,
+ pieceIds,
+ receivingStatus: PIECE_STATUS.claimDelayed,
+ },
+ });
+ }, [updatePiecesStatus]);
+
+ return {
+ isLoading,
+ delayClaims,
+ };
+};
diff --git a/lib/claiming/hooks/useClaimsSend/useClaimsSend.js b/lib/claiming/hooks/useClaimsSend/useClaimsSend.js
index 9df355b4..6f70d0eb 100644
--- a/lib/claiming/hooks/useClaimsSend/useClaimsSend.js
+++ b/lib/claiming/hooks/useClaimsSend/useClaimsSend.js
@@ -9,7 +9,7 @@ export const useClaimsSend = () => {
const ky = useOkapiKy();
const mutationFn = useCallback(({ data }) => {
- return ky.post(SEND_CLAIMS_API, { json: data });
+ return ky.post(SEND_CLAIMS_API, { json: data }).json();
}, [ky]);
const {
diff --git a/lib/claiming/index.js b/lib/claiming/index.js
index f76fd6f1..c55977d1 100644
--- a/lib/claiming/index.js
+++ b/lib/claiming/index.js
@@ -1,2 +1,3 @@
export * from './components';
export * from './hooks';
+export * from './utils';
diff --git a/lib/claiming/utils/getClaimingIntervalFromDate.js b/lib/claiming/utils/getClaimingIntervalFromDate.js
new file mode 100644
index 00000000..bb38aa7c
--- /dev/null
+++ b/lib/claiming/utils/getClaimingIntervalFromDate.js
@@ -0,0 +1,7 @@
+import { dayjs } from '@folio/stripes/components';
+
+export const getClaimingIntervalFromDate = (date) => {
+ const currentDay = dayjs().startOf('day');
+
+ return dayjs(date).diff(currentDay, 'days');
+};
diff --git a/lib/claiming/utils/getClaimingIntervalFromDate.test.js b/lib/claiming/utils/getClaimingIntervalFromDate.test.js
new file mode 100644
index 00000000..3abc6b00
--- /dev/null
+++ b/lib/claiming/utils/getClaimingIntervalFromDate.test.js
@@ -0,0 +1,12 @@
+import { dayjs } from '@folio/stripes/components';
+
+import { getClaimingIntervalFromDate } from './getClaimingIntervalFromDate';
+
+describe('getClaimingIntervalFromDate', () => {
+ it('should return claiming interval calculated based on provided date', () => {
+ const today = dayjs().startOf('day');
+
+ expect(getClaimingIntervalFromDate(today)).toEqual(0);
+ expect(getClaimingIntervalFromDate(today.add(5, 'days'))).toEqual(5);
+ });
+});
diff --git a/lib/claiming/utils/index.js b/lib/claiming/utils/index.js
new file mode 100644
index 00000000..6d40c1d4
--- /dev/null
+++ b/lib/claiming/utils/index.js
@@ -0,0 +1 @@
+export { getClaimingIntervalFromDate } from './getClaimingIntervalFromDate';
diff --git a/lib/hooks/usePiecesStatusBatchUpdate/usePiecesStatusBatchUpdate.js b/lib/hooks/usePiecesStatusBatchUpdate/usePiecesStatusBatchUpdate.js
new file mode 100644
index 00000000..a322b1ff
--- /dev/null
+++ b/lib/hooks/usePiecesStatusBatchUpdate/usePiecesStatusBatchUpdate.js
@@ -0,0 +1,24 @@
+import { useCallback } from 'react';
+import { useMutation } from 'react-query';
+
+import { useOkapiKy } from '@folio/stripes/core';
+
+import { PIECES_BATCH_STATUS_API } from '../../constants';
+
+export const usePiecesStatusBatchUpdate = () => {
+ const ky = useOkapiKy();
+
+ const mutationFn = useCallback(({ data }) => {
+ return ky.put(PIECES_BATCH_STATUS_API, { json: data }).json();
+ }, [ky]);
+
+ const {
+ mutateAsync,
+ isLoading,
+ } = useMutation({ mutationFn });
+
+ return {
+ isLoading,
+ updatePiecesStatus: mutateAsync,
+ };
+};
diff --git a/translations/stripes-acq-components/en.json b/translations/stripes-acq-components/en.json
index 4d68d25c..9255cab2 100644
--- a/translations/stripes-acq-components/en.json
+++ b/translations/stripes-acq-components/en.json
@@ -147,6 +147,12 @@
"claiming.action.sendClaim": "Send claim",
"claiming.action.delayClaim": "Delay claim",
"claiming.action.unreceivable": "Unreceivable",
+ "claiming.modal.delayClaim.heading": "{count, select, undefined {Delay claim} other {Delay {count, plural, one {claim} other {claims}}}}",
+ "claiming.modal.delayClaim.field.delayTo": "Delay to",
+ "claiming.modal.sendClaim.heading": "{count, select, undefined {Send claim} other {Send {count, plural, one {claim} other {claims}}}}",
+ "claiming.modal.sendClaim.field.claimExpiryDate": "Claim expiry date",
+ "claiming.modal.sendClaim.field.internalNote": "Internal note",
+ "claiming.modal.sendClaim.field.externalNote": "External note",
"referenceNumbers.addReferenceNumbers": "Add vendor reference number",
"referenceNumbers.refNumber": "Vendor reference number",