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

renewals: add renew buttons for patrons checked-out items #610

Merged
merged 1 commit into from
Nov 20, 2019
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
39 changes: 27 additions & 12 deletions rero_ils/modules/items/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

from .models import ItemIdentifier, ItemStatus
from ..api import IlsRecord, IlsRecordError, IlsRecordIndexer, IlsRecordsSearch
from ..circ_policies.api import CircPolicy
from ..documents.api import Document, DocumentsSearch
from ..errors import InvalidRecordID
from ..fetchers import id_fetcher
Expand Down Expand Up @@ -547,10 +548,33 @@ def last_location_pid(self):
return loan_location_pid
return self.location_pid

def can_extend(self, loan):
"""Checks if the patron has the rights to renew this item."""
from ..loans.utils import extend_loan_data_is_valid
can_extend = True
patron_pid = loan.get('patron_pid')
patron_type_pid = Patron.get_record_by_pid(
patron_pid).patron_type_pid
circ_policy = CircPolicy.provide_circ_policy(
self.library_pid,
patron_type_pid,
self.item_type_pid
)
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
)
) or self.number_of_requests():
can_extend = False
return can_extend

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).patron_type_pid
Expand All @@ -564,16 +588,7 @@ def action_filter(self, action, loan):
'new_action': None
}
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
)
) or self.number_of_requests():
if not self.can_extend(loan):
data['action_validated'] = False
if action == 'checkout':
if not circ_policy.get('allow_checkout'):
Expand Down
183 changes: 96 additions & 87 deletions rero_ils/modules/patrons/templates/rero_ils/patron_profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,115 +19,124 @@
{%- extends 'rero_ils/page.html' %}

{%- block body %}
{% include('rero_ils/_patron_profile_head.html') %}
{% include('rero_ils/_patron_profile_head.html') %}

<article class="mt-4">
<header>
<nav>
<ul class="nav nav-tabs" id="nav-tab" role="tablist">
<article class="mt-4">
<header>
<nav>
<ul class="nav nav-tabs" id="nav-tab" role="tablist">
<li class="nav-item">
<a class="nav-link active" href="#checkouts" data-toggle="tab"
id="checkouts-tab" title="{{ _('Checkouts') }}" role="tab"
aria-controls="checkouts" aria-selected="true">
<a class="nav-link active" href="#checkouts" data-toggle="tab" id="checkouts-tab" title="{{ _('Checkouts') }}"
role="tab" aria-controls="checkouts" aria-selected="true">
{{ _('Checkouts') }} ({{ checkouts|length }})
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#pending" data-toggle="tab"
id="pending-tab" title="{{ _('Pending') }}" role="tab"
aria-controls="pending" aria-selected="false">
<a class="nav-link" href="#pending" data-toggle="tab" id="pending-tab" title="{{ _('Pending') }}" role="tab"
aria-controls="pending" aria-selected="false">
{{ _('Pending') }} ({{ pendings|length }})
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#personal-data" data-toggle="tab"
id="personal-data-tab" title="{{ _('Personal data') }}" role="tab"
aria-controls="personal-data" aria-selected="false">
<a class="nav-link" href="#personal-data" data-toggle="tab" id="personal-data-tab"
title="{{ _('Personal data') }}" role="tab" aria-controls="personal-data" aria-selected="false">
{{ _('Personal data') }}
</a>
</li>
</ul>
</nav>
</header>
<article class="tab-content">
<section class="tab-pane show active py-2" id="checkouts" role="tabpanel" aria-labelledby="checkouts-tab">
{% if checkouts|length > 0 %}
{{ build_table('checkouts', checkouts) }}
{% else %}
<p>{{ _('No loan') }}</p>
{% endif %}
</section>
<section class="tab-pane py-2" id="pending" role="tabpanel" aria-labelledby="pending-tab">
{% if pendings|length > 0 %}
{{ build_table('requests', pendings) }}
{% else %}
<p>{{ _('No pending') }}</p>
{% endif %}
</section>
<section class="tab-pane py-2" id="personal-data" role="tabpanel" aria-labelledby="personal-data-tab">
{% include('rero_ils/_patron_profile_personal.html') %}
</section>
</article>
</ul>
</nav>
</header>
<article class="tab-content">
<section class="tab-pane show active py-2" id="checkouts" role="tabpanel" aria-labelledby="checkouts-tab">
{% if checkouts|length > 0 %}
{{ build_table('checkouts', checkouts) }}
{% else %}
<p>{{ _('No loan') }}</p>
{% endif %}
</section>
<section class="tab-pane py-2" id="pending" role="tabpanel" aria-labelledby="pending-tab">
{% if pendings|length > 0 %}
{{ build_table('requests', pendings) }}
{% else %}
<p>{{ _('No pending') }}</p>
{% endif %}
</section>
<section class="tab-pane py-2" id="personal-data" role="tabpanel" aria-labelledby="personal-data-tab">
{% include('rero_ils/_patron_profile_personal.html') %}
</section>
</article>
{%- endblock body %}
</article>


{%- endblock body %}
{% macro build_table(type, loans) %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th class="col-md-6 border-top-0" scope="col">{{ _('Title') }}</th>
<th class="col-md-2 border-top-0" scope="col">{{ _('Call Number') }}</th>
{% if type != 'checkouts' %}
<th class="col-md-2 border-top-0" scope="col">
{{ _('Pickup library') }}
</th>
{% endif %}
<th class="col-md-2 border-top-0" scope="col">
{% if type == 'checkouts' %}
{{ _('Due date') }}
{% else %}
{{ _('Reservation date') }}
{% endif %}
</th>
{% if type == 'checkouts' %}
<th class="col-md-1 border-top-0" scope="col">
{{ _('Renewals') }}
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for loan in loans %}
<tr>
<td>{{ loan.document_title }}</td>
<td>{{ loan.item_call_number }}</td>
{% if type != 'checkouts' %}
<td>
{{ loan.pickup_library_name }}
</td>
{% endif %}
<td>
{% if type == 'checkouts' %}
{{ loan.end_date|format_date(
<table class="table table-striped table-sm">
<thead>
<tr>
<th class="col-md-6 border-top-0" scope="col">{{ _('Title') }}</th>
<th class="col-md-2 border-top-0" scope="col">{{ _('Call Number') }}</th>
{% if type != 'checkouts' %}
<th class="col-md-2 border-top-0" scope="col">
{{ _('Pickup library') }}
</th>
{% endif %}
<th class="col-md-2 border-top-0" scope="col">
{% if type == 'checkouts' %}
{{ _('Due date') }}
{% else %}
{{ _('Reservation date') }}
{% endif %}
</th>
{% if type == 'checkouts' %}
<th class="col-md-1 border-top-0" scope="col">
{{ _('Renewals') }}
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for loan in loans %}
<tr>
<td>{{ loan.document_title }}</td>
<td>{{ loan.item_call_number }}</td>
{% if type != 'checkouts' %}
<td>
{{ loan.pickup_library_name }}
</td>
{% endif %}
<td>
{% if type == 'checkouts' %}
{{ loan.end_date|format_date(
format='short_date',
locale=current_i18n.locale.language
)}}
{% else %}
{{ loan.transaction_date|format_date(
{% else %}
{{ loan.transaction_date|format_date(
format='short_date',
locale=current_i18n.locale.language
)}}
{% endif %}
</td>
{% if type == 'checkouts' %}
<td>
{{ loan.extension_count }}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</td>
{% if type == 'checkouts' %}
<td>
{{ loan.extension_count }}
</td>
<td>
{% if loan.can_renew %}
{%- with form = can_renew_form %}
<form action="{{ url_for('patrons.profile') }}" method="POST" name="can_renew_form">
<input type="hidden" name="loan_pid" value="{{ loan.pid }}">
<button type="submit" class="btn btn btn-primary btn-bg">{{_('Renew')}}</button>
</form>
{%- endwith %}
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}

63 changes: 41 additions & 22 deletions rero_ils/modules/patrons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

from __future__ import absolute_import, print_function

from flask import Blueprint, current_app, jsonify, render_template, request
from flask import Blueprint, current_app, flash, jsonify, render_template, \
request
from flask_babelex import gettext as _
from flask_login import current_user, login_required
from flask_menu import register_menu
Expand All @@ -31,7 +32,7 @@
from ..documents.api import Document
from ..items.api import Item
from ..libraries.api import Library
from ..loans.api import get_loans_by_patron_pid
from ..loans.api import get_loans_by_patron_pid, Loan
from ..locations.api import Location

api_blueprint = Blueprint(
Expand Down Expand Up @@ -88,7 +89,8 @@ def logged_user():
return jsonify(data)


@blueprint.route('/global/patrons/profile', defaults={'viewcode': 'global'})
@blueprint.route('/global/patrons/profile', defaults={'viewcode': 'global'},
methods=['GET', 'POST'])
@blueprint.route('/<string:viewcode>/patrons/profile')
@login_required
@register_menu(
Expand All @@ -102,28 +104,45 @@ def profile(viewcode):
patron = Patron.get_patron_by_user(current_user)
if patron is None:
raise NotFound()
if request.method == 'POST':
loan = Loan.get_record_by_pid(request.values.get('loan_pid'))
item = Item.get_record_by_pid(loan.get('item_pid'))
data = {
'item_pid': item.pid,
'pid': request.values.get('loan_pid'),
'transaction_location_pid': item.location_pid
}
try:
item.extend_loan(**data)
flash(_('The item %(item_id)s has been renewed.',
BadrAly marked this conversation as resolved.
Show resolved Hide resolved
item_id=item.pid), 'success')
except Exception:
flash(_('Error during the renewal of the item %(item_id)s.',
BadrAly marked this conversation as resolved.
Show resolved Hide resolved
item_id=item.pid), 'danger')

loans = get_loans_by_patron_pid(patron.pid)
checkouts = []
requests = []
if loans:
for loan in loans:
item_pid = loan.get('item_pid')
item = Item.get_record_by_pid(item_pid).replace_refs()
document = Document.get_record_by_pid(item['document']['pid'])
loan['document_title'] = document['title']
loan['item_call_number'] = item['call_number']
if loan['state'] == 'ITEM_ON_LOAN':
checkouts.append(loan)
elif loan['state'] in (
'PENDING',
'ITEM_AT_DESK',
'ITEM_IN_TRANSIT_FOR_PICKUP'
):
pickup_loc = Location.get_record_by_pid(
loan['pickup_location_pid'])
loan['pickup_library_name'] = \
pickup_loc.get_library().get('name')
requests.append(loan)
for loan in loans:
item_pid = loan.get('item_pid')
item = Item.get_record_by_pid(item_pid)
document = Document.get_record_by_pid(
item.replace_refs()['document']['pid'])
loan['document_title'] = document['title']
loan['item_call_number'] = item['call_number']
if loan['state'] == 'ITEM_ON_LOAN':
loan['can_renew'] = item.can_extend(loan)
checkouts.append(loan)
elif loan['state'] in (
'PENDING',
'ITEM_AT_DESK',
'ITEM_IN_TRANSIT_FOR_PICKUP'
):
pickup_loc = Location.get_record_by_pid(
loan['pickup_location_pid'])
loan['pickup_library_name'] = \
pickup_loc.get_library().get('name')
requests.append(loan)
return render_template(
'rero_ils/patron_profile.html',
record=patron,
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/holdings/test_holdings_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def test_holding_item_links(client, holding_lib_martigny, item_lib_martigny,
assert not holding_lib_martigny.delete_from_index()
holding_lib_martigny.dbcommit(forceindex=True)

# test item count by holdings pid
assert holding_lib_martigny.get_items_count_by_holding_pid == 2


def test_holding_delete_after_item_deletion(
client, holding_lib_martigny, item_lib_martigny):
Expand Down
Loading