Skip to content

Commit

Permalink
circulation: renewal due date from current_date
Browse files Browse the repository at this point in the history
* NEW Calculates renewal due date from current date instead of checkout due date.
* NEW Removes renewal from the list of possible actions if renewal due date less than checkout due date.
* FIX Fixes rero#231.

Signed-off-by: Aly Badr <aly.badr@rero.ch>
  • Loading branch information
Aly Badr committed Apr 16, 2019
1 parent c412cb6 commit 544f62c
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 50 deletions.
14 changes: 8 additions & 6 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from __future__ import absolute_import, print_function

from datetime import timedelta
from functools import partial

from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_FETCHER, \
CIRCULATION_LOAN_MINTER, CIRCULATION_LOAN_PID_TYPE
Expand All @@ -51,9 +52,8 @@
from rero_ils.modules.loans.api import Loan

from .modules.items.api import Item, ItemsIndexer
from .modules.loans.utils import can_be_requested, \
get_default_extension_duration, get_default_extension_max_count, \
get_default_loan_duration, is_item_available_for_checkout, \
from .modules.loans.utils import can_be_requested, get_default_loan_duration, \
get_extension_params, is_item_available_for_checkout, \
loan_satisfy_circ_policies
from .modules.patrons.api import Patron
from .permissions import librarian_delete_permission_factory, \
Expand Down Expand Up @@ -975,9 +975,11 @@ def _(x):
item_available=is_item_available_for_checkout
),
extension=dict(
from_end_date=True,
duration_default=get_default_extension_duration,
max_count=get_default_extension_max_count
from_end_date=False,
duration_default=partial(
get_extension_params, parameter_name='duration_default'),
max_count=partial(
get_extension_params, parameter_name='max_count'),
),
request=dict(
can_be_requested=can_be_requested
Expand Down
33 changes: 32 additions & 1 deletion rero_ils/modules/items/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,36 @@ def library_pid(self):
location = Location.get_record_by_pid(self.location_pid).replace_refs()
return location.get('library').get('pid')

def action_filter(self, action, loan):
"""Filter actions."""
from ..circ_policies.api import CircPolicy
from ..loans.utils import extend_loan_data_is_valid
patron_pid = loan.get('patron_pid')
patron_type_pid = Patron.get_record_by_pid(
patron_pid).replace_refs().patron_type_pid
circ_policy = CircPolicy.provide_circ_policy(
self.library_pid,
patron_type_pid,
self.item_type_pid
)
action_validated = True
if action == 'extend':
extension_count = loan.get('extension_count', 0)
if not (
circ_policy.get('number_renewals') > 0 and
extension_count < circ_policy.get('number_renewals') and
extend_loan_data_is_valid(
loan.get('end_date'),
circ_policy.get('renewal_duration'),
self.library_pid
)
):
action_validated = False
if action == 'checkout':
if not circ_policy.get('allow_checkout'):
action_validated = False
return action_validated

@property
def actions(self):
"""Get all available actions."""
Expand All @@ -375,7 +405,8 @@ def actions(self):
if loan:
for transition in transitions.get(loan.get('state')):
action = transition.get('trigger')
actions.add(action)
if self.action_filter(action, loan):
actions.add(action)
# default actions
if not loan:
for transition in transitions.get('CREATED'):
Expand Down
20 changes: 1 addition & 19 deletions rero_ils/modules/items/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .api import Item
from ..circ_policies.api import CircPolicy
from ..loans.api import Loan
from ..loans.utils import extend_loan_data_is_valid
from ..patrons.api import Patron
from ...permissions import librarian_permission

Expand Down Expand Up @@ -244,27 +245,8 @@ def loans(patron_pid):
items_loans = Item.get_checked_out_items(patron_pid)
metadata = []
for item, loan in items_loans:
extension_count = loan.get('extension_count', 0)
item_dumps = item.dumps_for_circulation()
actions = item_dumps.get('actions')
circ_policy = CircPolicy.provide_circ_policy(
item.library_pid,
patron_type_pid,
item.item_type_pid
)
new_actions = []
for action in actions:
if action == 'checkout' and circ_policy.get('allow_checkout'):
new_actions.append(action)
if (
action == 'extend_loan' and
circ_policy.get('number_renewals') > 0 and
extension_count < circ_policy.get('number_renewals')
):
new_actions.append(action)
if action == 'checkin':
new_actions.append(action)
item_dumps['actions'] = new_actions
metadata.append({
'item': item_dumps,
'loan': loan.dumps_for_circulation()
Expand Down
56 changes: 39 additions & 17 deletions rero_ils/modules/loans/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

from datetime import datetime, timedelta

import ciso8601
from dateutil.parser import parse

from ..circ_policies.api import CircPolicy
from ..items.api import Item
from ..libraries.api import Library
Expand Down Expand Up @@ -66,27 +69,46 @@ def get_default_loan_duration(loan):
return new_duration.days


def get_default_extension_duration(loan):
"""Return extension duration in number of days."""
def get_extension_params(loan=None, parameter_name=None):
"""Return extension parameters."""
policy = get_circ_policy(loan)
# TODO: case when start_date is not sysdate.
start_date = datetime.now()
library_pid = Item.get_record_by_pid(loan.item_pid).library_pid
library = Library.get_record_by_pid(library_pid)
# invenio-circulation due_date.
due_date = start_date + timedelta(days=policy.get('renewal_duration'))
# rero_ils due_date, considering library opening_hours and exception_dates.
# next_open: -1 to check first the due date not the days.
open_after_due_date = library.next_open(date=due_date - timedelta(days=1))
new_duration = open_after_due_date - start_date
end_date = ciso8601.parse_datetime_as_naive(loan.get('end_date'))
params = {
'max_count': policy.get('number_renewals'),
'duration_default': policy.get('renewal_duration')
}
current_date = datetime.now()

return new_duration.days
library = Library.get_record_by_pid(
Item.get_record_by_pid(loan.item_pid).library_pid)

calculated_due_date = current_date + timedelta(
days=policy.get('renewal_duration'))

def get_default_extension_max_count(loan):
"""Return extensions max count."""
policy = get_circ_policy(loan)
return policy.get('number_renewals')
first_open_date = library.next_open(
date=calculated_due_date - timedelta(days=1))

if first_open_date.date() < end_date.date():
params['max_count'] = 0

new_duration = first_open_date - current_date
params['duration_default'] = new_duration.days

return params.get(parameter_name)


def extend_loan_data_is_valid(end_date, renewal_duration, library_pid):
"""Checks extend loan will be valid ."""
end_date = ciso8601.parse_datetime_as_naive(end_date)
current_date = datetime.now()
library = Library.get_record_by_pid(library_pid)
calculated_due_date = current_date + timedelta(
days=renewal_duration)
first_open_date = library.next_open(
date=calculated_due_date - timedelta(days=1))
if first_open_date.date() < end_date.date():
return False
return True


def loan_satisfy_circ_policies(loan):
Expand Down
169 changes: 162 additions & 7 deletions tests/api/test_items_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
# from utils import get_json, to_relative_url

import json
from datetime import datetime, timedelta

import ciso8601
import mock
Expand All @@ -36,7 +37,8 @@

from rero_ils.modules.circ_policies.api import CircPoliciesSearch
from rero_ils.modules.items.api import Item, ItemStatus
from rero_ils.modules.loans.api import LoanAction
from rero_ils.modules.loans.api import Loan, LoanAction
from rero_ils.modules.loans.utils import get_extension_params


def test_items_permissions(client, item_on_loan, user_patron_no_email,
Expand Down Expand Up @@ -1113,9 +1115,9 @@ def test_extend_possible_actions(client, item_on_shelf,

from rero_ils.modules.circ_policies.api import CircPolicy
circ_policy = CircPolicy.provide_circ_policy(
item.library_pid,
'ptty1',
'itty1'
item.library_pid,
'ptty1',
'itty1'
)

circ_policy['number_renewals'] = 0
Expand Down Expand Up @@ -1178,9 +1180,9 @@ def test_item_possible_actions(client, item_on_shelf,

from rero_ils.modules.circ_policies.api import CircPolicy
circ_policy = CircPolicy.provide_circ_policy(
item.library_pid,
'ptty1',
'itty1'
item.library_pid,
'ptty1',
'itty1'
)

circ_policy['allow_checkout'] = False
Expand Down Expand Up @@ -1209,3 +1211,156 @@ def test_item_possible_actions(client, item_on_shelf,
reindex=True
)
assert circ_policy['allow_checkout']


def test_items_extend_rejected(client, user_librarian_no_email,
user_patron_no_email,
location, item_type,
item_on_shelf, json_header,
circ_policy_short):
"""."""
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

# 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)
actions = data.get('action_applied')
loan_pid = actions[LoanAction.CHECKOUT].get('loan_pid')
loan = Loan.get_record_by_pid(loan_pid)
assert not item.get_extension_count()

max_count = get_extension_params(loan=loan, parameter_name='max_count')

assert circ_policy_short['number_renewals']
assert circ_policy_short['renewal_duration'] > 1
circ_policy_short['renewal_duration'] = 1

circ_policy_short.update(
data=circ_policy_short,
dbcommit=True,
reindex=True)
flush_index(CircPoliciesSearch.Meta.index)

max_count = get_extension_params(loan=loan, parameter_name='max_count')
assert max_count == 0

# extend loan rejected
res = client.post(
url_for('api_item.extend_loan'),
data=json.dumps(
dict(
item_pid=item_pid,
loan_pid=loan_pid
)
),
content_type='application/json',
)

assert res.status_code == 403

circ_policy_short['number_renewals'] = 1
circ_policy_short['renewal_duration'] = 15

circ_policy_short.update(
data=circ_policy_short,
dbcommit=True,
reindex=True)
flush_index(CircPoliciesSearch.Meta.index)

# checkin
res = client.post(
url_for('api_item.checkin'),
data=json.dumps(
dict(
item_pid=item_pid,
loan_pid=loan_pid
)
),
content_type='application/json',
)
assert res.status_code == 200


def test_items_extend_end_date(client, user_librarian_no_email,
user_patron_no_email,
location, item_type,
item_on_shelf, json_header,
circ_policy_short):
"""."""
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

# 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)
actions = data.get('action_applied')
loan_pid = actions[LoanAction.CHECKOUT].get('loan_pid')
loan = Loan.get_record_by_pid(loan_pid)
assert not item.get_extension_count()

max_count = get_extension_params(loan=loan, parameter_name='max_count')
renewal_duration = circ_policy_short['renewal_duration']
assert renewal_duration == 15

# extend loan
res = client.post(
url_for('api_item.extend_loan'),
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)
actions = data.get('action_applied')
loan_pid = actions[LoanAction.EXTEND].get('loan_pid')
loan = Loan.get_record_by_pid(loan_pid)
end_date = loan.get('end_date')
current_date = datetime.now()
calc_date = current_date + timedelta(days=renewal_duration)
assert (
calc_date.strftime('%Y-%m-%d') == ciso8601.parse_datetime_as_naive(
end_date).strftime('%Y-%m-%d')
)

# checkin
res = client.post(
url_for('api_item.checkin'),
data=json.dumps(
dict(
item_pid=item_pid,
loan_pid=loan_pid
)
),
content_type='application/json',
)
assert res.status_code == 200

0 comments on commit 544f62c

Please sign in to comment.