Skip to content

Commit

Permalink
circulation: possibility to check-out in-transit items
Browse files Browse the repository at this point in the history
* FIX Fixes #230: allows checkout of in-transit items.

Signed-off-by: Aly Badr <aly.badr@rero.ch>
  • Loading branch information
Aly Badr authored and jma committed May 6, 2019
1 parent 492f7e7 commit 6e4f68f
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 15 deletions.
42 changes: 32 additions & 10 deletions rero_ils/modules/items/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,18 @@ def wrapper(item, *args, **kwargs):
if not patron_pid:
raise CirculationException(
"Patron PID not specified")
data = {
'item_pid': item.pid,
'patron_pid': patron_pid
}
loan = Loan.create(data, dbcommit=True, reindex=True)
if function.__name__ == 'checkout':
request = get_request_by_item_pid_by_patron_pid(
item_pid=item.pid, patron_pid=patron_pid)
if request:
loan = Loan.get_record_by_pid(request.get('loan_pid'))

if not loan:
data = {
'item_pid': item.pid,
'patron_pid': patron_pid
}
loan = Loan.create(data, dbcommit=True, reindex=True)
else:
raise CirculationException(
"Loan PID not specified")
Expand Down Expand Up @@ -378,7 +385,10 @@ def action_filter(self, action, loan):
patron_type_pid,
self.item_type_pid
)
action_validated = True
data = {
'action_validated': True,
'new_action': None
}
if action == 'extend':
extension_count = loan.get('extension_count', 0)
if not (
Expand All @@ -390,11 +400,20 @@ def action_filter(self, action, loan):
self.library_pid
)
):
action_validated = False
data['action_validated'] = False
if action == 'checkout':
if not circ_policy.get('allow_checkout'):
action_validated = False
return action_validated
data['action_validated'] = False

if action == 'receive':
if (
circ_policy.get('allow_checkout') and
loan.get('state') == 'ITEM_IN_TRANSIT_FOR_PICKUP' and
loan.get('patron_pid') == patron_pid
):
data['action_validated'] = False
data['new_action'] = 'checkout'
return data

@property
def actions(self):
Expand All @@ -405,8 +424,11 @@ def actions(self):
if loan:
for transition in transitions.get(loan.get('state')):
action = transition.get('trigger')
if self.action_filter(action, loan):
data = self.action_filter(action, loan)
if data.get('action_validated'):
actions.add(action)
if data.get('new_action'):
actions.add(data.get('new_action'))
# default actions
if not loan:
for transition in transitions.get('CREATED'):
Expand Down
36 changes: 34 additions & 2 deletions rero_ils/modules/items/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ def librarian_request(item, data):
return item.request(**data)


def prior_checkout_actions(item, data):
"""Actions executed prior to a checkout."""
if data.get('loan_pid'):
loan = Loan.get_record_by_pid(data.get('loan_pid'))
if (
loan.get('state') == 'ITEM_IN_TRANSIT_FOR_PICKUP' and
loan.get('patron_pid') == data.get('patron_pid')
):
item.receive(**data)
if loan.get('state') == 'ITEM_IN_TRANSIT_TO_HOUSE':
item.cancel_loan(loan_pid=loan.get('loan_pid'))
del data['loan_pid']
else:
loan = get_loan_for_item(item.pid)
if loan:
item.cancel_loan(loan_pid=loan.get('loan_pid'))
return data


@api_blueprint.route('/checkout', methods=['POST'])
@check_authentication
@jsonify_action
Expand All @@ -130,7 +149,8 @@ def checkout(item, data):
required_parameters: patron_pid, item_pid
"""
return item.checkout(**data)
new_data = prior_checkout_actions(item, data)
return item.checkout(**new_data)


@api_blueprint.route("/checkin", methods=['POST'])
Expand Down Expand Up @@ -286,7 +306,19 @@ def item(item_barcode):
new_actions = []
for action in actions:
if action == 'checkout' and circ_policy.get('allow_checkout'):
new_actions.append(action)
if item.number_of_requests() > 0:
patron_barcode = Patron.get_record_by_pid(
patron_pid).get('barcode')
if item.patron_request_rank(patron_barcode) == 1:
new_actions.append(action)
else:
new_actions.append(action)
if (
action == 'receive' and
circ_policy.get('allow_checkout') and
item.number_of_requests() == 0
):
new_actions.append('checkout')
item_dumps['actions'] = new_actions
return jsonify({
'metadata': {
Expand Down
117 changes: 117 additions & 0 deletions tests/api/test_items_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,3 +1366,120 @@ def test_items_extend_end_date(client, user_librarian_no_email,
content_type='application/json',
)
assert res.status_code == 200


def test_items_in_transit(client, user_librarian_no_email,
user_patron_no_email,
location, item_type, store_location,
item_on_shelf, json_header,
circ_policy):
"""."""
login_user_via_session(client, user_librarian_no_email.user)
item = item_on_shelf
item_pid = item.pid
patron_pid = user_patron_no_email.pid

# request to pick at another location
res = client.post(
url_for('api_item.librarian_request'),
data=json.dumps(
dict(
item_pid=item_pid,
pickup_location_pid=store_location.pid,
patron_pid=patron_pid
)
),
content_type='application/json',
)
assert res.status_code == 200
data = get_json(res)
item_data = data.get('metadata')
actions = data.get('action_applied')
assert item_data.get('status') == ItemStatus.ON_SHELF
assert actions.get(LoanAction.REQUEST)
loan_pid = actions[LoanAction.REQUEST].get('loan_pid')
item = Item.get_record_by_pid(item_pid)

# validate (send) request
res = client.post(
url_for('api_item.validate_request'),
data=json.dumps(
dict(
item_pid=item_pid,
loan_pid=loan_pid
)
),
content_type='application/json',
)
assert res.status_code == 200
data = get_json(res)
item_data = data.get('metadata')
actions = data.get('action_applied')
assert item_data.get('status') == ItemStatus.IN_TRANSIT
assert actions.get(LoanAction.VALIDATE)

# checkout action to req patron is possible without the receive action
res = client.get(
url_for(
'api_item.item',
item_barcode=item.get('barcode'),
patron_pid=patron_pid
)
)
assert res.status_code == 200
data = get_json(res)
actions = data.get('metadata').get('item').get('actions')
assert 'checkout' in actions

# checkout
res = client.post(
url_for('api_item.checkout'),
data=json.dumps(
dict(
item_pid=item_pid,
patron_pid=patron_pid,
loan_pid=loan_pid
)
),
content_type='application/json',
)
assert res.status_code == 200
data = get_json(res)
assert Item.get_record_by_pid(item_pid).get('status') == ItemStatus.ON_LOAN

# checkin at location other than item location
res = client.post(
url_for('api_item.checkin'),
data=json.dumps(
dict(
item_pid=item_pid,
loan_pid=loan_pid,
transaction_location_pid=store_location.pid
)
),
content_type='application/json',
)
assert res.status_code == 200
data = get_json(res)
item_data = data.get('metadata')
actions = data.get('action_applied')
assert item_data.get('status') == ItemStatus.IN_TRANSIT
assert actions.get(LoanAction.CHECKIN)
loan_pid = actions[LoanAction.CHECKIN].get('loan_pid')
loan = actions[LoanAction.CHECKIN]
assert loan.get('state') == 'ITEM_IN_TRANSIT_TO_HOUSE'

# a new checkout
res = client.post(
url_for('api_item.checkout'),
data=json.dumps(
dict(
item_pid=item_pid,
patron_pid=patron_pid
)
),
content_type='application/json',
)
assert res.status_code == 200
data = get_json(res)
assert Item.get_record_by_pid(item_pid).get('status') == ItemStatus.ON_LOAN
6 changes: 3 additions & 3 deletions ui/src/app/circulation/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,17 @@ export class Item {
}
return ItemAction.no;
}

// should rely on backend and delete this function
public canLoan(patron) {
if (!this.available) {
if (this.status === ItemStatus.AT_DESK
if ((this.status === ItemStatus.AT_DESK || this.status === ItemStatus.IN_TRANSIT)
&& patron
&& this.pending_loans
&& this.pending_loans.length
&& this.pending_loans[0].patron_pid === patron.pid) {
return true;
}
return false;
return true;
}
// available
return true;
Expand Down

0 comments on commit 6e4f68f

Please sign in to comment.