Skip to content

Commit

Permalink
self-checkout: remove handling errors from the UI
Browse files Browse the repository at this point in the history
* add UI for displaying self-checkout delivery method for the loans on the backoffice
* closes CERNDocumentServer/cds-ils#927
  • Loading branch information
ntarocco authored and anikachurilova committed Nov 7, 2024
1 parent b9ae86b commit d39986d
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 89 deletions.
26 changes: 26 additions & 0 deletions src/lib/api/loans/loan.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/',
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -332,6 +356,8 @@ export const loanApi = {
doAction: doAction,
doRequest: doRequest,
doCheckout: doCheckout,
doSelfCheckout: doSelfCheckout,
doSelfCheckoutSearchItem: doSelfCheckoutSearchItem,
sendOverdueLoansNotificationReminder: sendOverdueLoansNotificationReminder,
serializer: serializer,
updateDates: updateDates,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/config/defaultConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ export const RECORDS_CONFIG = {
text: 'To my office',
iconClass: 'dolly',
},
'SELF-CHECKOUT': {
text: 'SELF-CHECKOUT',
iconClass: 'shopping basket',
},
},
extensionsMaxCount: 3,
loanWillExpireDays: 7,
Expand Down
15 changes: 14 additions & 1 deletion src/lib/modules/Loan/backoffice/LoanList/LoanListEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class LoanListEntry extends Component {
delivery && loan.metadata.state === 'PENDING'
? invenioConfig.CIRCULATION.deliveryMethods[delivery]
: '';
const isSelfCheckout = delivery === 'SELF-CHECKOUT';
return (
<Item>
<Item.Content>
Expand Down Expand Up @@ -79,13 +80,25 @@ export class LoanListEntry extends Component {
</List>
</Grid.Column>
<Grid.Column width={2} textAlign="center">
{deliveryMethod && (
{deliveryMethod ? (
<>
{delivery}{' '}
{deliveryMethod.iconClass && (
<Icon className={deliveryMethod.iconClass} />
)}
</>
) : (
isSelfCheckout && (
<>
{delivery}{' '}
<Icon
className={
invenioConfig.CIRCULATION.deliveryMethods[delivery]
.iconClass
}
/>
</>
)
)}
</Grid.Column>
<Grid.Column computer={3} largeScreen={3}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ class LoanRequestForm extends Component {
invenioConfig.CIRCULATION.deliveryMethods
);
this.deliveryMethods = this.withDeliveryMethod
? Object.keys(invenioConfig.CIRCULATION.deliveryMethods).map((key) => ({
key: key,
value: key,
text: invenioConfig.CIRCULATION.deliveryMethods[key].text,
}))
? Object.keys(invenioConfig.CIRCULATION.deliveryMethods)
.filter((key) => key !== 'SELF-CHECKOUT')
.map((key) => ({
key: key,
value: key,
text: invenioConfig.CIRCULATION.deliveryMethods[key].text,
}))
: [];
this.state['deliveryMethod'] = this.withDeliveryMethod
? this.deliveryMethods[0].value
Expand Down
52 changes: 1 addition & 51 deletions src/lib/pages/frontsite/SelfCheckout/SelfCheckout.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 (
<>
Expand Down Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ export default class SelfCheckoutModal extends React.Component {
onClose={() => toggleModal(false)}
>
<Modal.Header>
{`You are about to checkout a book with barcode:
{`You are about to checkout the literature with barcode:
${item?.metadata.barcode}`}
</Modal.Header>
<Modal.Content>
<DocumentCard item={item} />
<ManualCheckout
label="Wrong book?"
label="Wrong literature?"
autofocus
show
onBarcodeInput={onBarcodeDetected}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { connect } from 'react-redux';

import SelfCheckoutModalComponent from './SelfCheckoutModal';
import { checkoutItem } from '../state/actions';
import { selfCheckOut } from '../state/actions';

const mapDispatchToProps = (dispatch) => ({
checkoutItem: (documentPid, itemPid, patronPid) =>
dispatch(checkoutItem(documentPid, itemPid, patronPid)),
dispatch(selfCheckOut(documentPid, itemPid, patronPid)),
});

const mapStateToProps = (state) => ({
Expand Down
7 changes: 6 additions & 1 deletion src/lib/pages/frontsite/SelfCheckout/index.js
Original file line number Diff line number Diff line change
@@ -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)),
});

Expand Down
38 changes: 12 additions & 26 deletions src/lib/pages/frontsite/SelfCheckout/state/actions.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -20,27 +18,21 @@ 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({
type: SEARCH_IS_LOADING,
});

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,
Expand All @@ -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 = (
<p>
The loan {pid} has been created by you! You can view all your current
loans on your <Link to={FrontSiteRoutes.patronProfile}>profile</Link>{' '}
page.
Self-checkout completed! You can view all your current loans on your{' '}
<Link to={FrontSiteRoutes.patronProfile}>profile</Link> page.
</p>
);
dispatch(sendSuccessNotification('Success!', linkToLoan));
Expand Down
2 changes: 1 addition & 1 deletion src/lib/pages/frontsite/SelfCheckout/state/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('SelfCheck Out test', () => {
});

it('should dispatch an error action when the search fails', async () => {
const errorMsg = 'Error message';
const errorMsg = 'Network Error';
itemApiListMock.mockImplementation(() => {
throw new Error(errorMsg);
});
Expand Down

0 comments on commit d39986d

Please sign in to comment.