Skip to content

Commit

Permalink
circulation: hide buttons if action isn't available.
Browse files Browse the repository at this point in the history
If the patron can't operate an action due to circulation restrictions
(patron blocked, patron type limits, ...), circulation buttons should be
disable. In the patron profile view, adds warning/error messages depending
of patron restrictions.

If the 'renew' button is disable, adds a tooltip to give user the
reasons why.

Closes #1357
Closes #1356

Co-Authored-by: Renaud Michotte <renaud.michotte@gmail.com>
  • Loading branch information
zannkukai committed Nov 3, 2020
1 parent a7f2736 commit b43d054
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 49 deletions.
2 changes: 2 additions & 0 deletions rero_ils/modules/loans/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,8 @@ def patron_profile(patron):
loan=loan
)
loan['can_renew'] = can
if not can:
loan['can_renew_reasons'] = reasons
loans.append(loan)
elif loan['state'] in [
LoanState.PENDING,
Expand Down
32 changes: 21 additions & 11 deletions rero_ils/modules/patrons/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ def can_request(cls, item, **kwargs):

# a blocked patron can't request any item
if patron.is_blocked:
return False, [patron.blocked_message]
return False, [patron.get_blocked_message()]

return True, []

Expand All @@ -426,7 +426,7 @@ def can_checkout(cls, item, **kwargs):

# a blocked patron can't request any item
if patron.is_blocked:
return False, [patron.blocked_message]
return False, [patron.get_blocked_message()]

return True, []

Expand Down Expand Up @@ -578,12 +578,16 @@ def is_blocked(self):
"""Shortcut to know if user is blocked."""
return self.patron.get('blocked', False)

@property
def blocked_message(self):
"""Get the message in case of patron is blocked."""
def get_blocked_message(self, public=False):
"""Get the message in case of patron is blocked.
:param public: Is the message is for public interface ?
"""
main = _('Your account is currently blocked.') if public \
else _('This patron is currently blocked.')
if self.is_blocked:
return '{main} {reason_str}: {reason}'.format(
main=_('This patron is currently blocked.'),
main=main,
reason_str=_('Reason'),
reason=self.patron.get('blocked_note')
)
Expand Down Expand Up @@ -688,12 +692,13 @@ def transaction_user_validator(self, user_pid):
"""
return Patron.record_pid_exists(user_pid)

def get_circulation_messages(self):
def get_circulation_messages(self, public=False):
"""Return messages useful for circulation.
* check if the user is blocked ?
* check if the user reaches the maximum loans limit ?
:param public: is messages are for public interface ?
:return an array of messages. Each message is a dictionary with a level
and a content. The level could be used to filters messages if
needed.
Expand All @@ -706,7 +711,7 @@ def get_circulation_messages(self):
if self.is_blocked:
return [{
'type': 'error',
'content': self.blocked_message
'content': self.get_blocked_message(public)
}]

messages = []
Expand All @@ -719,14 +724,19 @@ def get_circulation_messages(self):
'content': message
})
# check fee amount limit
valid = patron_type.check_fee_amount_limit(self)
if not valid:
if not patron_type.check_fee_amount_limit(self):
messages.append({
'type': 'error',
'content': _(
'Transactions denied: the maximal fee amount is reached.')
})

# check the patron type overdue limit
if not patron_type.check_overdue_items_limit(self):
messages.append({
'type': 'error',
'content': _('Checkout denied: the maximal number of overdue '
'items is reached')
})
return messages


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,23 @@
{% endif %}
</div>
<div class="col-lg-2 text-right">
{% if loan.can_renew %}
{%- with form = can_renew_form %}
{% 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="type" value="renew">
<input type="hidden" name="loan_pid" value="{{ loan.pid }}">
<button type="submit" class="btn btn-primary mt-1">
{{ _('Renew') }}
</button>
</form>
{%- endwith %}
{% endif %}
{%- endwith %}
{%- else -%}
<span class="d-inline-block" tabindex="0" data-toggle="tooltip" data-html="true" title="{{ loan.can_renew_reasons | join('<br/>') }}">
<button type="submit" class="btn btn-primary mt-1" disabled>
{{ _('Renew') }}
</button>
</span>
{%- endif %}
</div>
</div>
<div id="loan-{{ loan.pid }}" class="mt-1 ng-star-inserted d-none">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<div class="col-lg-2{% if loan.state == 'ITEM_AT_DESK' %} text-success{% endif %}">
{% if loan.state == 'ITEM_AT_DESK' %}
<i class="fa fa-check" title="{{ _('item at desk') }}" aria-hidden="true"></i>
{{ _('ready') }}
{{ _('to pick uup') }}
{% elif loan.state in ['PENDING', 'ITEM_IN_TRANSIT_FOR_PICKUP'] %}
<i class="fa fa-bullseye" title="{{ _('pending') }}" aria-hidden="true"></i>
<!-- TODO: Add expired date and remove "waiting" text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@
{% include('rero_ils/_patron_profile_head.html') %}

<section>
{% for key, data in alerts.items() %}
{% for message in data['messages'] %}
<div class="alert alert-{{ data['level'] }}" role="alert">{{ message }}</div>
{% endfor %}
{% for message in messages %}
<div class="alert alert-{{ message['type'] }}" role="alert">{{ message['content'] }}</div>
{% endfor %}
{% set note=record.notes|selectattr('type', '==', 'public_note')|list|last%}
{% if note %}
<div class="alert alert-warning" role="alert">
{{note. content.replace('\n', '<br>')|safe}}
{{note.content.replace('\n', '<br>')|safe}}
</div>
{% endif %}
</section>
Expand Down
2 changes: 1 addition & 1 deletion rero_ils/modules/patrons/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ def get_patron_from_arguments(**kwargs):
return kwargs.get('patron') \
or Patron.get_patron_by_barcode(kwargs.get('patron_barcode')) \
or Patron.get_record_by_pid(kwargs.get('patron_pid')) \
or Patron.get_record_by_pid(kwargs.get('loan').get('patron_id'))
or Patron.get_record_by_pid(kwargs.get('loan').get('patron_pid'))
38 changes: 14 additions & 24 deletions rero_ils/modules/patrons/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,29 +224,19 @@ def profile(viewcode):

loans, requests, fees, history, ill_requests = patron_profile(patron)

# patron alert list
# each alert dictionary key represent an alert category (subscription,
# fees, blocked, ...). For each category, we define a bootstrap level
# (https://getbootstrap.com/docs/4.0/components/alerts/) and a list of
# message. Each message will be displayed into a separate alert box.
alerts = {}
pending_subscriptions = patron.get_pending_subscriptions()
if pending_subscriptions:
alerts['subscriptions'] = {
'messages': map(
lambda sub: _('You have a pending subscription fee.'),
pending_subscriptions
),
'level': 'warning' # bootstrap alert level
}
if patron.patron.get('blocked'):
alerts['blocking'] = {
'messages': [
_('Your account is currently blocked. Reason: %(reason)s',
reason=patron.patron.get('blocked_note', ''))
],
'level': 'danger'
}
# patron messages list
messages = patron.get_circulation_messages(True)
if patron.get_pending_subscriptions():
messages.append({
'type': 'warning',
'content': _('You have a pending subscription fee.')
})
bootstrap_alert_mapping = {
'error': 'danger'
}
for message in messages:
msg_type = message['type']
message['type'] = bootstrap_alert_mapping.get(msg_type, msg_type)

return render_template(
'rero_ils/patron_profile.html',
Expand All @@ -256,7 +246,7 @@ def profile(viewcode):
fees=fees,
history=history,
ill_requests=ill_requests,
alerts=alerts,
messages=messages,
viewcode=viewcode,
tab=tab
)
Expand Down
9 changes: 9 additions & 0 deletions rero_ils/static/scss/rero_ils/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ html [type=button] {
color: $secondary;
}

button:disabled:hover {
cursor: not-allowed;
}

div.tooltip div.tooltip-inner{
text-align: left;
max-width: 400px;
}

/*
*********************************
Expand Down
7 changes: 4 additions & 3 deletions tests/api/patrons/test_patrons_blocked.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def test_blocked_field_exists(
patron3_martigny_blocked_no_email):
"""Test ptrn6 have blocked field present and set to False."""
login_user_via_session(client, librarian_martigny_no_email.user)
patron3 = patron3_martigny_blocked_no_email

# non blocked patron
non_blocked_patron_url = url_for(
Expand All @@ -53,9 +54,9 @@ def test_blocked_field_exists(
assert 'blocked' in data['metadata']['patron']
assert data['metadata']['patron']['blocked'] is True

assert patron3_martigny_blocked_no_email.is_blocked
note = patron3_martigny_blocked_no_email.patron.get('blocked_note')
assert note and note in patron3_martigny_blocked_no_email.blocked_message
assert patron3.is_blocked
note = patron3.patron.get('blocked_note')
assert note and note in patron3.get_blocked_message()


def test_blocked_field_not_present(
Expand Down

0 comments on commit b43d054

Please sign in to comment.