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

loan: better anonymize function #3675

Merged
merged 1 commit into from
May 15, 2024
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
99 changes: 51 additions & 48 deletions rero_ils/modules/loans/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,13 @@ def create(cls, data, id_=None, delete_pid=True,
dbcommit=False, reindex=False, **kwargs):
"""Create the loan record.

:param cls - class object
:param data - dictionary representing a loan record.
:param id_ - UUID, it would be generated if it is not given.
:param delete_pid - remove the pid present in the data if True,
:param dbcommit - commit the changes in the db after the creation.
:param reindex - index the record after the creation.
:param cls: class object
:param data: dictionary representing a loan record.
:param id_: UUID, it would be generated if it is not given.
:param delete_pid: remove the pid present in the data if True,
:param dbcommit: commit the changes in the db after the creation.
:param reindex: index the record after the creation.
:returns: the created record
"""
data['$schema'] = current_jsonschemas.path_to_url(cls._schema)
# default state assignment
Expand All @@ -213,7 +214,13 @@ def create(cls, data, id_=None, delete_pid=True,
reindex=reindex, **kwargs)

def update(self, data, commit=False, dbcommit=False, reindex=False):
"""Update loan record."""
"""Update loan record.

:param commit: if True push the db transaction.
:param dbcommit: make the change effective in db.
:param reindex: reindex the record.
:returns: the modified record
"""
self._loan_build_org_ref(data)
# set the field to_anonymize
if not self.get('to_anonymize') and Loan.can_anonymize(loan_data=data):
Expand All @@ -222,22 +229,23 @@ def update(self, data, commit=False, dbcommit=False, reindex=False):
data=data, commit=commit, dbcommit=dbcommit, reindex=reindex)
return self

def anonymize(self, loan, commit=True, dbcommit=False, reindex=False):
def anonymize(self, commit=True, dbcommit=False, reindex=False):
"""Anonymize a loan.

:param loan: the loan to update.
:param dbcommit - commit the changes in the db after the creation.
:param reindex - index the record after the creation.
:param commit: if True push the db transaction.
:param dbcommit: make the change effective in db.
:param reindex: reindex the record.
:returns: the modified record
"""
from rero_ils.modules.loans.logs.api import LoanOperationLog
loan['to_anonymize'] = True
self['to_anonymize'] = True
try:
super().update(loan, commit, dbcommit, reindex)
super().update(self, commit, dbcommit, reindex)
# Anonymize loan operation logs
LoanOperationLog.anonymize_logs(loan['pid'])
LoanOperationLog.anonymize_logs(self.pid)
except Exception as err:
current_app.logger.error(
f'Can not anonymize loan: {loan.get("pid")} {err}')
f'Can not anonymize loan: {self.get("pid")} {err}')
return self

def date_fields2datetime(self):
Expand Down Expand Up @@ -292,7 +300,7 @@ def patron_by_pid(pid, known_patrons):

:param pid: the patron pid.
:param known_patrons: already known patrons.
:return the corresponding patron.
:return: the corresponding patron.
"""
fields = ['pid', 'first_name', 'last_name', 'patron.barcode']
if pid not in known_patrons:
Expand All @@ -309,7 +317,7 @@ def location_by_pid(pid, known_locations):

:param pid: the location pid.
:param known_locations: already known locations.
:return the corresponding location.
:return: the corresponding location.
"""
fields = ['pid', 'name', 'library', 'pickup_name']
if pid not in known_locations:
Expand All @@ -327,7 +335,7 @@ def library_name_by_pid(pid, known_libraries):

:param pid: the library pid.
:param known_libraries: already known libraries.
:return the corresponding library.
:return: the corresponding library.
"""
if pid not in known_libraries:
results = LibrariesSearch()\
Expand All @@ -343,7 +351,7 @@ def holding_by_pid(pid, known_holdings):

:param pid: the holdings pid.
:param known_holdings: already known holdings.
:return the corresponding holdings.
:return: the corresponding holdings.
"""
from ..holdings.api import HoldingsSearch
if pid not in known_holdings:
Expand All @@ -360,7 +368,7 @@ def item_by_pid(pid, known_items):

:param pid: the item pid.
:param known_items: already known items.
:return the corresponding item.
:return: the corresponding item.
"""
fields = ['pid', 'barcode', 'call_number',
'second_call_number', 'library', 'location',
Expand All @@ -380,7 +388,7 @@ def item_type_by_pid(pid, known_ittys):

:param pid: the item_type pid.
:param known_ittys: already known item types
:return the corresponding item type
:return: the corresponding item type
"""
if pid not in known_ittys:
results = ItemTypesSearch()\
Expand Down Expand Up @@ -491,15 +499,14 @@ def is_loan_due_soon(self, tstamp=None):
:returns: True if is due soon
"""
date = tstamp or datetime.now(timezone.utc)
due_soon_date = self.get('due_soon_date')
if due_soon_date:
if due_soon_date := self.get('due_soon_date'):
return ciso8601.parse_datetime(due_soon_date) <= date
return False

def has_pending_transaction(self):
"""Check if a loan has pending patron transactions.

:return True if some open transaction is found, False otherwise
:return: True if some open transaction is found, False otherwise
"""
if pid := self.pid:
return PatronTransactionsSearch() \
Expand Down Expand Up @@ -614,9 +621,9 @@ def library_pid(self):
@property
def checkout_library_pid(self):
"""Get the checkout library pid."""
checkout_location = Location.get_record_by_pid(
self.get('checkout_location_pid'))
if checkout_location:
if checkout_location := Location.get_record_by_pid(
self.get('checkout_location_pid')
):
return checkout_location.library_pid

@cached_property
Expand Down Expand Up @@ -671,7 +678,7 @@ def get_overdue_fees(self):
loan is overdue, the circulation policy used will be related to the
checkout location, not the extended location.

:return An array of tuple. Each tuple are composed with two values :
:return: An array of tuple. Each tuple are composed with two values :
the fee amount and a related timestamp.
Ex: [
(0.1, datetime.date('2021-01-28')),
Expand Down Expand Up @@ -761,9 +768,9 @@ def get_notification_candidates(self, trigger):
generated based on another loan than itself (in case of request).

:param trigger: the fired action trigger (optional)
:return a list of tuple. Each tuple represent a notification candidate.
Each tuple is composed by 2 elements : the loan object, and the
related notification type.
:return: a list of tuple. Each tuple represent a notification
candidate. Each tuple is composed by 2 elements :
the loan object, and the related notification type.
"""
from rero_ils.modules.items.api import Item
candidates = []
Expand Down Expand Up @@ -867,18 +874,16 @@ def create_notification(self, trigger=None, _type=None, counter=0):
reminder_type = DUE_SOON_REMINDER_TYPE
if n_type != NotificationType.DUE_SOON:
reminder_type = OVERDUE_REMINDER_TYPE
reminder = cipo.get_reminder(reminder_type, counter)
# Reminder does not exists on the circulation policy.
if not reminder:
create = False
else:
if cipo.get_reminder(reminder_type, counter):
record['context']['reminder_counter'] = counter
else:
create = False

# create the notification and enqueue it.
if create:
notification = self._create_notification_resource(
record, dispatch=dispatch)
if notification:
if notification := self._create_notification_resource(
record, dispatch=dispatch):
notifications.append(notification)
return notifications

Expand Down Expand Up @@ -912,7 +917,7 @@ def get_anonymized_candidates(cls):
(we need to keep transactions for the last 3 months for circulation
management).

:return a generator of `Loan` candidate to anonymize.
:return: a generator of `Loan` candidate to anonymize.
"""
three_month_ago = datetime.now() - relativedelta(months=3)
six_month_ago = datetime.now() - relativedelta(months=6)
Expand All @@ -938,15 +943,15 @@ def get_anonymized_candidates(cls):
def is_concluded(self):
"""Check if loan can be considered as concluded or not.

:return True is the loan is concluded, False otherwise
:return: True is the loan is concluded, False otherwise
"""
return self.get('state') in LoanState.CONCLUDED and \
not self.has_pending_transaction()

def age(self):
"""Return the age of a loan in days.

:return the number of days since last transaction date.
:return: the number of days since last transaction date.
"""
if value := self.get('transaction_date'):
trans_date = ciso8601.parse_datetime(value)
Expand All @@ -968,7 +973,7 @@ def can_anonymize(cls, loan_data=None, patron=None):

:param loan_data: the loan data to check (could be a `Loan` or a dict).
:param patron: the patron to check.
:return True if the loan can be anonymized, False otherwise.
:return: True if the loan can be anonymized, False otherwise.
"""
# force `loan_data` as a `Loan` object instance if it's not yet.
loan = loan_data if isinstance(loan_data, cls) else cls(loan_data)
Expand Down Expand Up @@ -1019,7 +1024,7 @@ def action_required_params(action=None):
"""List of required parameters for circulation actions.

:param action: the action name to check.
:return the list of required parameters than the `Loan` must define to
:return: the list of required parameters than the `Loan` must define to
validate the action.
"""
shared_params = ['transaction_location_pid', 'transaction_user_pid']
Expand Down Expand Up @@ -1088,9 +1093,7 @@ def get_loans_by_item_pid_by_patron_pid(item_pid, patron_pid,
filter_states=filter_states,
)
search_result = search.execute()
if search_result.hits:
return search_result.hits.hits[0]['_source']
return {}
return search_result.hits.hits[0]['_source'] if search_result.hits else {}


def get_loans_stats_by_patron_pid(patron_pid):
Expand Down Expand Up @@ -1220,7 +1223,7 @@ def get_overdue_loan_pids(patron_pid=None, tstamp=None):
:param patron_pid: the patron pid. If none, return all overdue loans.
:param tstamp: a timestamp to define the execution time of the function.
Default to `datetime.now()`.
:return a generator of loan pid
:return: a generator of loan pid
"""
end_date = tstamp or datetime.now(timezone.utc)
end_date = end_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
Expand All @@ -1245,7 +1248,7 @@ def get_overdue_loans(patron_pid=None, tstamp=None):

:param patron_pid: the patron pid. If none, return all overdue loans.
:param tstamp: a timestamp to define the execution time of the function
:return a generator of Loan
:return: a generator of Loan
"""
for pid in get_overdue_loan_pids(patron_pid, tstamp):
yield Loan.get_record_by_pid(pid)
Expand Down
2 changes: 1 addition & 1 deletion rero_ils/modules/loans/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def loan_anonymizer(dbcommit=True, reindex=True):
counter = 0
for loan in Loan.get_anonymized_candidates():
if Loan.can_anonymize(loan_data=loan, patron=None):
loan.anonymize(loan, dbcommit=dbcommit, reindex=reindex)
loan.anonymize(dbcommit=dbcommit, reindex=reindex)
counter += 1

set_timestamp('anonymize-loans', count=counter)
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/loans/test_loans_operation_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_anonymize_logs(item2_on_loan_martigny_patron_and_loan_on_loan):
assert log['loan']['patron']['pid'] == patron['pid']
assert log['loan']['patron']['name'] == 'Roduit, Louis'

loan.anonymize(loan)
loan.anonymize(dbcommit=True, reindex=True)

logs = LoanOperationLogsSearch().get_logs_by_record_pid(loan['pid'])
assert len(logs) == 3
Expand Down
Loading