Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

self-checkout: use dedicated endpoints #1223

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 123 additions & 26 deletions invenio_app_ils/circulation/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@
get_all_expiring_or_overdue_loans_by_patron_pid,
)
from invenio_app_ils.errors import (
DocumentOverbookedError,
IlsException,
InvalidLoanExtendError,
InvalidParameterError,
ItemCannotCirculateError,
ItemHasActiveLoanError,
ItemNotFoundError,
MissingRequiredParameterError,
MultipleItemsBarcodeFoundError,
PatronHasLoanOnDocumentError,
PatronHasLoanOnItemError,
PatronHasRequestOnDocumentError,
Expand Down Expand Up @@ -119,7 +124,7 @@ def request_loan(
patron_pid,
transaction_location_pid,
transaction_user_pid=None,
**kwargs
**kwargs,
):
"""Create a new loan and trigger the first transition to PENDING."""
loan_cls = current_circulation.loan_record_cls
Expand Down Expand Up @@ -170,13 +175,54 @@ def patron_has_active_loan_on_item(patron_pid, item_pid):
return search_result.hits.total.value > 0


def _checkout_loan(
item_pid,
patron_pid,
transaction_location_pid,
trigger="checkout",
transaction_user_pid=None,
delivery=None,
**kwargs,
):
"""Checkout a loan."""
transaction_user_pid = transaction_user_pid or str(current_user.id)
loan_cls = current_circulation.loan_record_cls
# create a new loan
record_uuid = uuid.uuid4()
new_loan = dict(
patron_pid=patron_pid,
transaction_location_pid=transaction_location_pid,
transaction_user_pid=transaction_user_pid,
)

if delivery:
new_loan["delivery"] = delivery
# check if there is an existing request
loan = patron_has_request_on_document(patron_pid, kwargs.get("document_pid"))
if loan:
loan = loan_cls.get_record_by_pid(loan.pid)
pid = IlsCirculationLoanIdProvider.get(loan["pid"]).pid
loan.update(new_loan)
else:
pid = ils_circulation_loan_pid_minter(record_uuid, data=new_loan)
loan = loan_cls.create(data=new_loan, id_=record_uuid)

params = deepcopy(loan)
params.update(item_pid=item_pid, **kwargs)

loan = current_circulation.circulation.trigger(
loan, **dict(params, trigger=trigger)
)
return pid, loan


def checkout_loan(
item_pid,
patron_pid,
transaction_location_pid,
transaction_user_pid=None,
force=False,
**kwargs
**kwargs,
):
"""Create a new loan and trigger the first transition to ITEM_ON_LOAN.

Expand All @@ -191,7 +237,7 @@ def checkout_loan(
the checkout. If False, the checkout will fail when the item cannot
circulate.
"""
loan_cls = current_circulation.loan_record_cls

if patron_has_active_loan_on_item(patron_pid=patron_pid, item_pid=item_pid):
raise PatronHasLoanOnItemError(patron_pid, item_pid)
optional_delivery = kwargs.get("delivery")
Expand All @@ -201,35 +247,86 @@ def checkout_loan(
if force:
_set_item_to_can_circulate(item_pid)

transaction_user_pid = transaction_user_pid or str(current_user.id)

# create a new loan
record_uuid = uuid.uuid4()
new_loan = dict(
patron_pid=patron_pid,
transaction_location_pid=transaction_location_pid,
return _checkout_loan(
item_pid,
patron_pid,
transaction_location_pid,
transaction_user_pid=transaction_user_pid,
**kwargs,
)

# check if there is an existing request
loan = patron_has_request_on_document(patron_pid, kwargs.get("document_pid"))
if loan:
loan = loan_cls.get_record_by_pid(loan.pid)
pid = IlsCirculationLoanIdProvider.get(loan["pid"]).pid
loan.update(new_loan)
else:
pid = ils_circulation_loan_pid_minter(record_uuid, data=new_loan)
loan = loan_cls.create(data=new_loan, id_=record_uuid)

params = deepcopy(loan)
params.update(item_pid=item_pid, **kwargs)
def _ensure_item_loanable_via_self_checkout(item_pid):
"""Self-checkout: return loanable item or raise when not loanable.

# trigger the transition to request
loan = current_circulation.circulation.trigger(
loan, **dict(params, trigger="checkout")
Implements the self-checkout rules to loan an item.
"""
item = current_app_ils.item_record_cls.get_record_by_pid(item_pid)
item_dict = item.replace_refs()

if item_dict["status"] != "CAN_CIRCULATE":
raise ItemCannotCirculateError()

circulation_state = item_dict["circulation"].get("state")
has_active_loan = (
circulation_state and circulation_state in CIRCULATION_STATES_LOAN_ACTIVE
)
if has_active_loan:
raise ItemHasActiveLoanError(loan_pid=item_dict["circulation"]["loan_pid"])

return pid, loan
document = current_app_ils.document_record_cls.get_record_by_pid(
item_dict["document_pid"]
)
document_dict = document.replace_refs()
if document_dict["circulation"].get("overbooked", False):
raise DocumentOverbookedError(
f"Cannot self-checkout the overbooked document {item_dict['document_pid']}"
)

return item


def self_checkout_get_item_by_barcode(barcode):
"""Search for an item by barcode.

:param barcode: the barcode of the item to search for
:return item: the item that was found, or raise in case of errors
"""
item_search = current_app_ils.item_search_cls()
items = item_search.search_by_barcode(barcode).execute()
if items.hits.total.value == 0:
raise ItemNotFoundError(barcode=barcode)
if items.hits.total.value > 1:
raise MultipleItemsBarcodeFoundError(barcode)

item_pid = items.hits[0].pid
item = _ensure_item_loanable_via_self_checkout(item_pid)
return item_pid, item


def self_checkout(
item_pid, patron_pid, transaction_location_pid, transaction_user_pid=None, **kwargs
):
"""Perform self-checkout.

:param item_pid: a dict containing `value` and `type` fields to
uniquely identify the item.
:param patron_pid: the PID value of the patron
:param transaction_location_pid: the PID value of the location where the
checkout is performed
:param transaction_user_pid: the PID value of the user that performed the
checkout
"""
_ensure_item_loanable_via_self_checkout(item_pid["value"])
return _checkout_loan(
item_pid,
patron_pid,
transaction_location_pid,
transaction_user_pid=transaction_user_pid,
trigger="self_checkout",
delivery=dict(method="SELF-CHECKOUT"),
**kwargs,
)


def bulk_extend_loans(patron_pid, **kwargs):
Expand All @@ -253,7 +350,7 @@ def bulk_extend_loans(patron_pid, **kwargs):
params,
trigger="extend",
transition_kwargs=dict(send_notification=False),
)
),
)
extended_loans.append(extended_loan)
except (CirculationException, InvalidLoanExtendError):
Expand Down
16 changes: 14 additions & 2 deletions invenio_app_ils/circulation/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
PatronOwnerPermission,
authenticated_user_permission,
backoffice_permission,
loan_checkout_permission,
loan_extend_circulation_permission,
patron_owner_permission,
superuser_permission,
Expand Down Expand Up @@ -84,6 +83,7 @@
ILS_CIRCULATION_DELIVERY_METHODS = {
"PICKUP": "Pick it up at the library desk",
"DELIVERY": "Have it delivered to my office",
"SELF-CHECKOUT": "Self-checkout",
}

# Notification message creator for loan notifications
Expand Down Expand Up @@ -162,7 +162,13 @@
dest="ITEM_ON_LOAN",
trigger="checkout",
transition=ILSToItemOnLoan,
permission_factory=loan_checkout_permission,
permission_factory=backoffice_permission,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

),
dict(
dest="ITEM_ON_LOAN",
trigger="self_checkout",
transition=ILSToItemOnLoan,
permission_factory=authenticated_user_permission,
),
],
"PENDING": [
Expand All @@ -172,6 +178,12 @@
transition=ILSToItemOnLoan,
permission_factory=backoffice_permission,
),
dict(
dest="ITEM_ON_LOAN",
trigger="self_checkout",
transition=ILSToItemOnLoan,
permission_factory=authenticated_user_permission,
),
dict(
dest="CANCELLED",
trigger="cancel",
Expand Down
2 changes: 2 additions & 0 deletions invenio_app_ils/circulation/loaders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
from .schemas.json.bulk_extend import BulkExtendLoansSchemaV1
from .schemas.json.loan_checkout import LoanCheckoutSchemaV1
from .schemas.json.loan_request import LoanRequestSchemaV1
from .schemas.json.loan_self_checkout import LoanSelfCheckoutSchemaV1
from .schemas.json.loan_update_dates import LoanUpdateDatesSchemaV1

loan_request_loader = ils_marshmallow_loader(LoanRequestSchemaV1)
loan_checkout_loader = ils_marshmallow_loader(LoanCheckoutSchemaV1)
loan_self_checkout_loader = ils_marshmallow_loader(LoanSelfCheckoutSchemaV1)
loan_update_dates_loader = ils_marshmallow_loader(LoanUpdateDatesSchemaV1)
loans_bulk_update_loader = ils_marshmallow_loader(BulkExtendLoansSchemaV1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 CERN.
#
# invenio-app-ils is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.

"""Invenio App ILS circulation Loan Checkout loader JSON schema."""

from invenio_circulation.records.loaders.schemas.json import LoanItemPIDSchemaV1
from marshmallow import fields

from .base import LoanBaseSchemaV1


class LoanSelfCheckoutSchemaV1(LoanBaseSchemaV1):
"""Loan self-checkout schema."""

item_pid = fields.Nested(LoanItemPIDSchemaV1, required=True)
1 change: 1 addition & 0 deletions invenio_app_ils/circulation/notifications/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class NotificationLoanMsg(NotificationMsg):
request="request.html",
request_no_items="request_no_items.html",
checkout="checkout.html",
self_checkout="self_checkout.html",
checkin="checkin.html",
extend="extend.html",
cancel="cancel.html",
Expand Down
1 change: 1 addition & 0 deletions invenio_app_ils/circulation/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# under the terms of the MIT License; see LICENSE file for more details.

"""Loan serializers."""

from invenio_records_rest.serializers.response import search_responsify

from invenio_app_ils.records.schemas.json import ILSRecordSchemaJSONV1
Expand Down
1 change: 1 addition & 0 deletions invenio_app_ils/circulation/serializers/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# under the terms of the MIT License; see LICENSE file for more details.

"""Response serializers for circulation module."""

import json

from flask import current_app
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% block title %}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be added to CDS-ILS too

InvenioILS: loan started for "{{ document.title|safe }}"
{% endblock %}

{% block body_plain %}
Dear {{ patron.name }},

your self-checkout loan for "{{ document.full_title }}" <{{ spa_routes.HOST }}{{ spa_routes.PATHS['literature']|format(pid=document.pid) }}> has started.

The due date is {{ loan.end_date }}.
{% endblock %}

{% block body_html %}
Dear {{ patron.name }}, <br/><br/>

your self-checkout loan for <a href="{{ spa_routes.HOST }}{{ spa_routes.PATHS['literature']|format(pid=document.pid) }}">"{{ document.full_title }}"</a> has <b>started</b>. <br/><br/>

<b>The due date is {{ loan.end_date }}</b>.<br/>
{% endblock %}
Loading