Skip to content

Commit

Permalink
notifications: generalize notification resource
Browse files Browse the repository at this point in the history
This refactoring purpose is to generalize the notification system in order
to manage every kind of notification. The `Notification` resource
structure is heavy linked to circulation operation (we need to link the
notification with a loan).

This commit implements a specific class implementation for each type of
notification, with a subclass that provides relevant information to
process and dispatch the notification.

Closes rero#2373.
Closes rero#2390.
Closes rero#2410.

Co-Authored-by: Renaud Michotte <renaud.michotte@gmail.com>
  • Loading branch information
zannkukai committed Oct 13, 2021
1 parent c4bdd9e commit 0481872
Show file tree
Hide file tree
Showing 61 changed files with 2,016 additions and 944 deletions.
44 changes: 44 additions & 0 deletions rero_ils/modules/documents/dumpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2021 RERO
# Copyright (C) 2021 UCLouvain
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Documents dumpers."""

from invenio_records.dumpers import Dumper as InvenioRecordsDumper

from rero_ils.modules.documents.utils import title_format_text_head


class DocumentNotificationDumper(InvenioRecordsDumper):
"""Document dumper class for notification."""

def dump(self, record, data):
"""Dump a document instance for notification.
:param record: The record to dump.
:param data: The initial dump data passed in by ``record.dumps()``.
"""
title_text = title_format_text_head(
record.get('title', []),
responsabilities=record.get('responsibilityStatement')
)
data.update({
'pid': record.get('pid'),
'title_text': title_text
})
data = {k: v for k, v in data.items() if v}
return data
16 changes: 9 additions & 7 deletions rero_ils/modules/items/api/circulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,13 +1184,15 @@ def get_links_to_me(self, get_pids=False):
links['fees'] = fees
return links

def get_requests(self, sort_by=None, count=False, pids=False):
def get_requests(self, sort_by=None, output=None):
"""Return sorted pending, item_on_transit, item_at_desk loans.
:param sort_by: the sort to result. default sort is _created.
:param count: if True, return the number of request.
:param pids: if True, return the only the pids.
:return a generator of corresponding request or a request counter.
:param output: the type of output. 'pids', 'count' or 'obj' (default)
:return depending of output parameter:
- 'obj': a generator ``Loan`` objects.
- 'count': the request counter.
- 'pids': the request pids list
"""

def _list_obj():
Expand All @@ -1210,9 +1212,9 @@ def _list_obj():
LoanState.ITEM_AT_DESK,
LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
]).source(['pid'])
if pids:
if output == 'pids':
return [hit.pid for hit in query.scan()]
elif count:
elif output == 'count':
return query.count()
else:
return _list_obj()
Expand Down Expand Up @@ -1341,7 +1343,7 @@ def get_extension_count(self):

def number_of_requests(self):
"""Get number of requests for a given item."""
return self.get_requests(count=True)
return self.get_requests(output='count')

def patron_request_rank(self, patron):
"""Get the rank of patron in list of requests on this item."""
Expand Down
10 changes: 8 additions & 2 deletions rero_ils/modules/items/api/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def create(cls, data, id_=None, delete_pid=False,
# If `dbcommit` is already set to True, this commit is already done by
# the `IlsRecord.update()` function.
#
# /!\ if we write some other operation after _increment_next_predition
# /!\ if we write some other operation after _increment_next_prediction
# we need to manage ourself the `rollback()`.
#
# TODO :: best solution will be to create an invenio `post_create`
Expand Down Expand Up @@ -367,7 +367,7 @@ def status(self):

@property
def enumerationAndChronology(self):
"""Shortcut for item enumarationAndChronology."""
"""Shortcut for item enumerationAndChronology."""
return self.get('enumerationAndChronology', '')

@property
Expand Down Expand Up @@ -435,6 +435,12 @@ def issue_inherited_first_call_number(self):
return Holding.get_record_by_pid(
self.holding_pid).get('call_number')

@property
def call_numbers(self):
"""Return an array with all known item call_numbers."""
data = [self.get(key) for key in ['call_number', 'second_call_number']]
return [call_number for call_number in data if call_number]

@property
def location_pid(self):
"""Shortcut for item location pid."""
Expand Down
43 changes: 43 additions & 0 deletions rero_ils/modules/items/dumpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2021 RERO
# Copyright (C) 2021 UCLouvain
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Items dumpers."""

from invenio_records.dumpers import Dumper as InvenioRecordsDumper


class ItemNotificationDumper(InvenioRecordsDumper):
"""Item dumper class for notification."""

def dump(self, record, data):
"""Dump an item instance for notification.
:param record: The record to dump.
:param data: The initial dump data passed in by ``record.dumps()``.
:return a dict with dumped data.
"""
location = record.get_location()
data = {
'pid': record.pid,
'barcode': record.get('barcode'),
'call_numbers': record.call_numbers,
'location_name': location.get('name'),
'library_name': location.get_library().get('name')
}
data = {k: v for k, v in data.items() if v}
return data
40 changes: 40 additions & 0 deletions rero_ils/modules/libraries/dumpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2021 RERO
# Copyright (C) 2021 UCLouvain
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Libraries dumpers."""

from invenio_records.dumpers import Dumper as InvenioRecordsDumper


class LibraryCirculationNotificationDumper(InvenioRecordsDumper):
"""Library dumper class for circulation notification."""

def dump(self, record, data):
"""Dump a library instance for circulation notification.
:param record: The record to dump.
:param data: The initial dump data passed in by ``record.dumps()``.
:return a dict with dumped data.
"""
data.update({
'pid': record.pid,
'name': record.get('name'),
'address': record.get('address'),
'email': record.get('email')
})
return {k: v for k, v in data.items() if v}
16 changes: 10 additions & 6 deletions rero_ils/modules/loans/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class LoanState(object):
ITEM_RETURNED = 'ITEM_RETURNED'
CANCELLED = 'CANCELLED'

ITEM_IN_TRANSIT = [ITEM_IN_TRANSIT_TO_HOUSE, ITEM_IN_TRANSIT_FOR_PICKUP]


class LoanAction(object):
"""Class holding all available circulation loan actions."""
Expand Down Expand Up @@ -759,12 +761,11 @@ def get_notification_candidates(self, trigger):
related notification type.
"""
from ..items.api import Item

candidates = []
item = Item.get_record_by_pid(self.item_pid)
# Get the number of requests on the related item and exclude myself
# Get the list of requests pids for the related item and exclude myself
# from the result list.
requests = item.get_requests(pids=True)
requests = item.get_requests(output='pids')
requests = [loan_pid for loan_pid in requests if loan_pid != self.pid]
has_request = len(requests) > 0

Expand Down Expand Up @@ -821,6 +822,7 @@ def create_notification(self, trigger=None, _type=None, counter=0):
from .utils import get_circ_policy
types = [(self, t) for t in [_type] if t]
notifications = []

for loan, n_type in types or self.get_notification_candidates(trigger):
create = True # Should the notification actually be created.
# Internal notification (library destination) should be directly
Expand All @@ -831,7 +833,9 @@ def create_notification(self, trigger=None, _type=None, counter=0):
record = {
'creation_date': datetime.now(timezone.utc).isoformat(),
'notification_type': n_type,
'loan': {'$ref': get_ref_for_pid('loans', loan.pid)}
'context': {
'loan': {'$ref': get_ref_for_pid('loans', loan.pid)}
}
}
# overdue + due_soon
if n_type in NotificationType.REMINDERS_NOTIFICATIONS:
Expand All @@ -852,7 +856,7 @@ def create_notification(self, trigger=None, _type=None, counter=0):
if not reminder:
create = False
else:
record['reminder_counter'] = counter
record['context']['reminder_counter'] = counter

# create the notification and enqueue it.
if create:
Expand Down Expand Up @@ -1125,7 +1129,7 @@ def loan_has_open_events(loan_pid=None):
:return True|False.
"""
search = NotificationsSearch()\
.filter('term', loan__pid=loan_pid)\
.filter('term', context__loan__pid=loan_pid)\
.source(['pid']).scan()
for record in search:
transactions_count = PatronTransactionsSearch()\
Expand Down
Loading

0 comments on commit 0481872

Please sign in to comment.