diff --git a/src/lib/api/loans/loan.js b/src/lib/api/loans/loan.js index ef7cd24d2..6b189bb38 100644 --- a/src/lib/api/loans/loan.js +++ b/src/lib/api/loans/loan.js @@ -9,6 +9,7 @@ import { serializer } from './serializer'; const apiPaths = { checkout: '/circulation/loans/checkout', + selfCheckout: '/circulation/loans/self-checkout', notificationOverdue: '/circulation/loans/:loanPid/notification-overdue', item: '/circulation/loans/:loanPid', list: '/circulation/loans/', @@ -114,6 +115,29 @@ const doCheckout = async ( return response; }; +const doSelfCheckoutSearchItem = async (barcode) => { + const response = await http.get( + `${apiPaths.selfCheckout}?barcode=${barcode}` + ); + response.data = serializer.fromJSON(response.data); + return response; +}; + +const doSelfCheckout = async (documentPid, itemPid, patronPid) => { + const currentUser = sessionManager.user; + const payload = { + document_pid: documentPid, + item_pid: itemPid, + patron_pid: patronPid, + transaction_location_pid: `${currentUser.locationPid}`, + transaction_user_pid: `${currentUser.id}`, + }; + + const response = await http.post(apiPaths.selfCheckout, payload); + response.data = serializer.fromJSON(response.data); + return response; +}; + const assignItemToLoan = async (itemPid, loanPid) => { const path = generatePath(apiPaths.replaceItem, { loanPid: loanPid }); const payload = { item_pid: itemPid }; @@ -332,6 +356,8 @@ export const loanApi = { doAction: doAction, doRequest: doRequest, doCheckout: doCheckout, + doSelfCheckout: doSelfCheckout, + doSelfCheckoutSearchItem: doSelfCheckoutSearchItem, sendOverdueLoansNotificationReminder: sendOverdueLoansNotificationReminder, serializer: serializer, updateDates: updateDates, diff --git a/src/lib/config/defaultConfig.js b/src/lib/config/defaultConfig.js index 8fc869a97..61e66ed6f 100644 --- a/src/lib/config/defaultConfig.js +++ b/src/lib/config/defaultConfig.js @@ -176,6 +176,12 @@ export const RECORDS_CONFIG = { iconClass: 'dolly', }, }, + deliveryMethodSelfCheckout: { + 'SELF-CHECKOUT': { + text: 'SELF-CHECKOUT', + iconClass: 'shopping basket', + }, + }, extensionsMaxCount: 3, loanWillExpireDays: 7, loanActiveStates: [ diff --git a/src/lib/modules/Loan/backoffice/LoanList/LoanListEntry.js b/src/lib/modules/Loan/backoffice/LoanList/LoanListEntry.js index 58cef0e3b..05959ee63 100644 --- a/src/lib/modules/Loan/backoffice/LoanList/LoanListEntry.js +++ b/src/lib/modules/Loan/backoffice/LoanList/LoanListEntry.js @@ -22,6 +22,7 @@ export class LoanListEntry extends Component { delivery && loan.metadata.state === 'PENDING' ? invenioConfig.CIRCULATION.deliveryMethods[delivery] : ''; + const isSelfCheckout = delivery === 'SELF-CHECKOUT'; return ( @@ -79,13 +80,26 @@ export class LoanListEntry extends Component { - {deliveryMethod && ( + {deliveryMethod ? ( <> {delivery}{' '} {deliveryMethod.iconClass && ( )} + ) : ( + isSelfCheckout && ( + <> + {delivery}{' '} + + + ) )} diff --git a/src/lib/pages/backoffice/Loan/LoanDetails/LoanMetadata/LoanMetadata.js b/src/lib/pages/backoffice/Loan/LoanDetails/LoanMetadata/LoanMetadata.js index 46c70d76e..2a6bf27ce 100644 --- a/src/lib/pages/backoffice/Loan/LoanDetails/LoanMetadata/LoanMetadata.js +++ b/src/lib/pages/backoffice/Loan/LoanDetails/LoanMetadata/LoanMetadata.js @@ -33,7 +33,9 @@ export class LoanMetadata extends Component { const delivery = _get(_delivery, 'method'); if (delivery) { const deliveryMethod = - invenioConfig.CIRCULATION.deliveryMethods[delivery]; + invenioConfig.CIRCULATION.deliveryMethods[delivery] || + invenioConfig.CIRCULATION.deliveryMethodSelfCheckout[delivery]; + return ( <> {deliveryMethod.text}{' '} diff --git a/src/lib/pages/frontsite/SelfCheckout/SelfCheckout.js b/src/lib/pages/frontsite/SelfCheckout/SelfCheckout.js index 7885834ba..d9e1264d1 100644 --- a/src/lib/pages/frontsite/SelfCheckout/SelfCheckout.js +++ b/src/lib/pages/frontsite/SelfCheckout/SelfCheckout.js @@ -13,9 +13,6 @@ import { import { BarcodeScanner } from '@components/BarcodeScanner'; import { SelfCheckoutModal } from './SelfCheckoutModal'; import { ManualCheckout } from './ManualCheckout'; -import { invenioConfig } from '@config'; -import _isEmpty from 'lodash/isEmpty'; -import _find from 'lodash/find'; class SelfCheckout extends React.Component { constructor(props) { @@ -41,50 +38,10 @@ class SelfCheckout extends React.Component { this.setState({ barcode: detectedBarcode }); const { selfCheckOutSearch } = this.props; await selfCheckOutSearch(detectedBarcode); - - // open modal if item is loanable - const shouldShowModal = this.isItemLoanable(detectedBarcode); - if (shouldShowModal) { - this.toggleModal(true); - } else { - this.toggleModal(false); - } + this.toggleModal(true); } }; - itemStatus = (item) => ({ - canCirculate: () => - invenioConfig.ITEMS.canCirculateStatuses.includes(item.metadata.status), - isOnShelf: () => !item.metadata.circulation.state, // on shelf if circulation.state doesn't exist - }); - - isItemLoanable = (itemBarcode) => { - const { user, item, notifyResultMessage } = this.props; - var resultMessage = `Book with barcode ${itemBarcode} not found.`; - - if (!_isEmpty(item)) { - if (this.itemStatus(item).canCirculate()) { - if (this.itemStatus(item).isOnShelf()) { - return true; - } else { - if (item.metadata.circulation.patron_pid === user.id.toString()) { - resultMessage = `You already loaned this book with barcode: ${itemBarcode}!`; - } else { - resultMessage = `Book with barcode: ${itemBarcode} is currently on loan!`; - } - } - } else { - const status = _find(invenioConfig.ITEMS.statuses, { - value: item.metadata?.status, - }); - resultMessage = `Book with barcode: ${itemBarcode} is ${status?.text}!`; - } - } - - notifyResultMessage(resultMessage); - return false; - }; - renderInstructions = () => { return ( <> @@ -169,13 +126,6 @@ class SelfCheckout extends React.Component { SelfCheckout.propTypes = { /* REDUX */ selfCheckOutSearch: PropTypes.func.isRequired, - user: PropTypes.object.isRequired, - item: PropTypes.object, - notifyResultMessage: PropTypes.func.isRequired, -}; - -SelfCheckout.defaultProps = { - item: null, }; export default Overridable.component('SelfCheckout', SelfCheckout); diff --git a/src/lib/pages/frontsite/SelfCheckout/SelfCheckoutModal/SelfCheckoutModal.js b/src/lib/pages/frontsite/SelfCheckout/SelfCheckoutModal/SelfCheckoutModal.js index f7ea0c892..edab6d175 100644 --- a/src/lib/pages/frontsite/SelfCheckout/SelfCheckoutModal/SelfCheckoutModal.js +++ b/src/lib/pages/frontsite/SelfCheckout/SelfCheckoutModal/SelfCheckoutModal.js @@ -46,13 +46,13 @@ export default class SelfCheckoutModal extends React.Component { onClose={() => toggleModal(false)} > - {`You are about to checkout a book with barcode: + {`You are about to checkout the literature with barcode: ${item?.metadata.barcode}`} ({ checkoutItem: (documentPid, itemPid, patronPid) => - dispatch(checkoutItem(documentPid, itemPid, patronPid)), + dispatch(selfCheckOut(documentPid, itemPid, patronPid)), }); const mapStateToProps = (state) => ({ diff --git a/src/lib/pages/frontsite/SelfCheckout/index.js b/src/lib/pages/frontsite/SelfCheckout/index.js index a4063f95d..b75de07c4 100644 --- a/src/lib/pages/frontsite/SelfCheckout/index.js +++ b/src/lib/pages/frontsite/SelfCheckout/index.js @@ -1,10 +1,15 @@ import { connect } from 'react-redux'; import SelfCheckoutComponent from './SelfCheckout'; -import { selfCheckOutSearch, notifyResultMessage } from './state/actions'; +import { + selfCheckOut, + selfCheckOutSearch, + notifyResultMessage, +} from './state/actions'; const mapDispatchToProps = (dispatch) => ({ selfCheckOutSearch: (term) => dispatch(selfCheckOutSearch(term)), + selfCheckOut: (term) => dispatch(selfCheckOut(term)), notifyResultMessage: (message) => dispatch(notifyResultMessage(message)), }); diff --git a/src/lib/pages/frontsite/SelfCheckout/state/actions.js b/src/lib/pages/frontsite/SelfCheckout/state/actions.js index cde6a0a17..74b4b4903 100644 --- a/src/lib/pages/frontsite/SelfCheckout/state/actions.js +++ b/src/lib/pages/frontsite/SelfCheckout/state/actions.js @@ -1,13 +1,11 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { itemApi } from '@api/items'; import { loanApi } from '@api/loans'; import { sendErrorNotification, sendSuccessNotification, sendWarningNotification, } from '@components/Notifications'; -import _first from 'lodash/first'; import { FrontSiteRoutes } from '@routes/urls'; export const SEARCH_HAS_ERROR = 'selfCheckOut/SEARCH_HAS_ERROR'; @@ -20,19 +18,6 @@ export const notifyResultMessage = (message) => { }; }; -const searchItem = async (dispatch, term) => { - const upperCasedTerm = term.toUpperCase(); - const response = await itemApi.list( - itemApi.query().withBarcode(upperCasedTerm).qs() - ); - const item = _first(response.data.hits) || null; - - dispatch({ - type: SEARCH_ITEM_SUCCESS, - payload: item, - }); -}; - export const selfCheckOutSearch = (term) => { return async (dispatch) => { dispatch({ @@ -40,7 +25,14 @@ export const selfCheckOutSearch = (term) => { }); try { - await searchItem(dispatch, term); + const upperCasedTerm = term.toUpperCase(); + const response = await loanApi.doSelfCheckoutSearchItem(upperCasedTerm); + const item = response.data || null; + + dispatch({ + type: SEARCH_ITEM_SUCCESS, + payload: item, + }); } catch (error) { dispatch({ type: SEARCH_HAS_ERROR, @@ -51,20 +43,14 @@ export const selfCheckOutSearch = (term) => { }; }; -export const checkoutItem = (documentPid, itemPid, patronPid) => { +export const selfCheckOut = (documentPid, itemPid, patronPid) => { return async (dispatch) => { try { - const response = await loanApi.doCheckout( - documentPid, - itemPid, - patronPid - ); - const { pid } = response.data.metadata; + await loanApi.doSelfCheckout(documentPid, itemPid, patronPid); const linkToLoan = (

- The loan {pid} has been created by you! You can view all your current - loans on your profile{' '} - page. + Self-checkout completed! You can view all your current loans on your{' '} + profile page.

); dispatch(sendSuccessNotification('Success!', linkToLoan)); diff --git a/src/lib/pages/frontsite/SelfCheckout/state/actions.test.js b/src/lib/pages/frontsite/SelfCheckout/state/actions.test.js index 6ea617bc6..179a78f4d 100644 --- a/src/lib/pages/frontsite/SelfCheckout/state/actions.test.js +++ b/src/lib/pages/frontsite/SelfCheckout/state/actions.test.js @@ -2,27 +2,25 @@ import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import * as actions from './actions'; import { initialState } from './reducer'; -import { itemApi } from '@api/items'; +import { loanApi } from '@api/loans'; const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); -const itemApiListMock = jest.fn(); -itemApi.list = itemApiListMock; +const loanApiDoSelfCheckoutSearchItemMock = jest.fn(); +loanApi.doSelfCheckoutSearchItem = loanApiDoSelfCheckoutSearchItemMock; const lowerCasedBarcode = 'cm-145123'; const upperCasedBarcode = lowerCasedBarcode.toUpperCase(); const expectedPayload = { pid: 'item-pid', barcode: upperCasedBarcode }; const response = { - data: { - hits: [expectedPayload], - }, + data: expectedPayload, }; let store; beforeEach(() => { - itemApiListMock.mockClear(); + loanApiDoSelfCheckoutSearchItemMock.mockClear(); store = mockStore(initialState); store.clearActions(); @@ -30,7 +28,7 @@ beforeEach(() => { describe('SelfCheck Out test', () => { it('should dispatch an action updating the payload result item', async () => { - itemApiListMock.mockResolvedValue(response); + loanApiDoSelfCheckoutSearchItemMock.mockResolvedValue(response); const expectedAction1 = { type: actions.SEARCH_IS_LOADING, @@ -42,14 +40,14 @@ describe('SelfCheck Out test', () => { await store.dispatch(actions.selfCheckOutSearch(lowerCasedBarcode)); expect(store.getActions()[0]).toEqual(expectedAction1); expect(store.getActions()[1]).toEqual(expectedAction2); - expect(itemApiListMock).toHaveBeenCalledWith( - itemApi.query().withBarcode(upperCasedBarcode).qs() + expect(loanApiDoSelfCheckoutSearchItemMock).toHaveBeenCalledWith( + upperCasedBarcode ); }); it('should dispatch an error action when the search fails', async () => { const errorMsg = 'Error message'; - itemApiListMock.mockImplementation(() => { + loanApiDoSelfCheckoutSearchItemMock.mockImplementation(() => { throw new Error(errorMsg); });