def test_due_soon_loans(client, librarian_martigny_no_email, patron_martigny_no_email, loc_public_martigny, item_type_standard_martigny, item_lib_martigny, json_header, circ_policy_short_martigny): """Test overdue loans.""" login_user_via_session(client, librarian_martigny_no_email.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny_no_email.pid assert not item.is_loaned_to_patron( patron_martigny_no_email.get('barcode')) assert item.can_delete assert item.available from rero_ils.modules.circ_policies.api import CircPolicy circ_policy = CircPolicy.provide_circ_policy( item.library_pid, patron_martigny_no_email.patron_type_pid, item.item_type_pid) circ_policy['number_of_days_before_due_date'] = 7 circ_policy['checkout_duration'] = 3 circ_policy.update(circ_policy, dbcommit=True, reindex=True) # 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) loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') due_soon_loans = get_due_soon_loans() assert due_soon_loans[0].get('pid') == loan_pid # test due date hour checkout_loan = Loan.get_record_by_pid(loan_pid) end_date = ciso8601.parse_datetime(checkout_loan.get('end_date')) assert end_date.minute == 59 and end_date.hour == 23 new_timezone = pytz.timezone('US/Pacific') end_date = ciso8601.parse_datetime( checkout_loan.get('end_date')).astimezone(new_timezone) assert end_date.minute == 59 and end_date.hour != 23 new_timezone = pytz.timezone('Europe/Amsterdam') end_date = ciso8601.parse_datetime( checkout_loan.get('end_date')).astimezone(new_timezone) assert end_date.minute == 59 and end_date.hour != 23 # checkin the item to put it back to it's original state res = client.post( url_for('api_item.checkin'), data=json.dumps(dict(item_pid=item_pid, pid=loan_pid)), content_type='application/json', ) assert res.status_code == 200
def test_create_over_and_due_soon_notifications_task( client, librarian_martigny_no_email, patron_martigny_no_email, item_lib_martigny, circ_policy_short_martigny, loc_public_martigny, lib_martigny): """Test overdue and due_soon loans.""" login_user_via_session(client, librarian_martigny_no_email.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny_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) loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') loan = Loan.get_record_by_pid(loan_pid) # test due_soon notification end_date = datetime.now(timezone.utc) + timedelta(days=3) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) due_soon_loans = get_due_soon_loans() assert due_soon_loans[0].get('pid') == loan_pid create_over_and_due_soon_notifications() flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(notification_type='due_soon') # test overdue notification end_date = datetime.now(timezone.utc) - timedelta(days=7) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) overdue_loans = get_overdue_loans() assert overdue_loans[0].get('pid') == loan_pid create_over_and_due_soon_notifications() flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(notification_type='overdue') assert number_of_reminders_sent(loan) == 1 # checkin the item to put it back to it's original state res = client.post( url_for('api_item.checkin'), data=json.dumps(dict(item_pid=item_pid, pid=loan_pid)), content_type='application/json', ) assert res.status_code == 200
def test_due_soon_loans(client, librarian_martigny, patron_martigny, loc_public_martigny, item_type_standard_martigny, item_lib_martigny, circ_policy_short_martigny): """Test overdue loans.""" login_user_via_session(client, librarian_martigny.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny.pid assert not get_last_transaction_loc_for_item(item_pid) assert not item.patron_has_an_active_loan_on_item(patron_martigny) assert item.can_delete assert item.available from rero_ils.modules.circ_policies.api import CircPolicy circ_policy = CircPolicy.provide_circ_policy( item.organisation_pid, item.library_pid, patron_martigny.patron_type_pid, item.item_type_pid) circ_policy['reminders'][0]['days_delay'] = 7 circ_policy['checkout_duration'] = 3 circ_policy.update(circ_policy, dbcommit=True, reindex=True) # checkout res, data = postdata( client, 'api_item.checkout', dict( item_pid=item_pid, patron_pid=patron_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid, )) assert res.status_code == 200 loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') due_soon_loans = get_due_soon_loans() assert due_soon_loans[0].get('pid') == loan_pid # test due date regarding multiple timezones checkout_loan = Loan.get_record_by_pid(loan_pid) loan_date = ciso8601.parse_datetime(checkout_loan.get('end_date')) # as instance timezone is Europe/Zurich, it should be either 21 or 22 check_timezone_date(pytz.utc, loan_date, [21, 22]) # should be 14:59/15:59 in US/Pacific (because of daylight saving time) check_timezone_date(pytz.timezone('US/Pacific'), loan_date, [14, 15]) check_timezone_date(pytz.timezone('Europe/Amsterdam'), loan_date) # checkin the item to put it back to it's original state res, _ = postdata( client, 'api_item.checkin', dict(item_pid=item_pid, pid=loan_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid)) assert res.status_code == 200
def test_notifications_task( client, librarian_martigny, patron_martigny, item_lib_martigny, circ_policy_short_martigny, loc_public_martigny, lib_martigny): """Test overdue and due_soon loans.""" login_user_via_session(client, librarian_martigny.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny.pid # First we need to create a checkout res, data = postdata( client, 'api_item.checkout', dict( item_pid=item_pid, patron_pid=patron_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid, ) ) assert res.status_code == 200 loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') loan = Loan.get_record_by_pid(loan_pid) # test due_soon notification # update the loan end_date to reflect the due_soon date. So when we run # the task to create notification this loan should be considerate as # due_soon and a notification should be created. end_date = datetime.now(timezone.utc) + timedelta(days=5) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) flush_index(LoansSearch.Meta.index) due_soon_loans = list(get_due_soon_loans()) assert due_soon_loans[0].get('pid') == loan_pid create_notifications(types=[ NotificationType.DUE_SOON, NotificationType.OVERDUE ]) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(NotificationType.DUE_SOON) notif = get_notification(loan, NotificationType.DUE_SOON) notif_date = ciso8601.parse_datetime(notif.get('creation_date')) assert notif_date.date() == datetime.today().date() # -- test overdue notification -- # For this test, we simulate an overdue on Friday and the library is closed # during the weekend. No notification should be generated. # Friday end_date = datetime(year=2021, month=1, day=22, tzinfo=timezone.utc) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) # Process the notification during the weekend (Saturday) process_date = datetime(year=2021, month=1, day=23, tzinfo=timezone.utc) overdue_loans = list(get_overdue_loans(tstamp=process_date)) assert overdue_loans[0].get('pid') == loan_pid create_notifications(types=[ NotificationType.OVERDUE ], tstamp=process_date) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) # Should not be created assert not loan.is_notified(NotificationType.OVERDUE, 1) # Should not be sent assert number_of_notifications_sent( loan, notification_type=NotificationType.OVERDUE) == 0 # For this test, we will update the loan to simulate an overdue of 12 # days. With this delay, regarding the cipo configuration, only the first # overdue reminder should be sent. # NOTE : the cipo define the first overdue reminder after 5 days. But we # use an overdue of 12 days because the overdue is based on # loan->item->library open days. Using 12 (5 days + 1 week) we # ensure than the overdue notification will be sent. loan_lib = Library.get_record_by_pid(loan.library_pid) add_days = 12 open_days = [] while len(open_days) < 12: end_date = datetime.now(timezone.utc) - timedelta(days=add_days) open_days = loan_lib.get_open_days(end_date) add_days += 1 loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) overdue_loans = list(get_overdue_loans()) assert overdue_loans[0].get('pid') == loan_pid create_notifications(types=[ NotificationType.DUE_SOON, NotificationType.OVERDUE ]) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(NotificationType.OVERDUE, 0) assert number_of_notifications_sent( loan, notification_type=NotificationType.OVERDUE) == 1 # test overdue notification#2 # Now simulate than the previous call crashed. So call the task with a # fixed date. In our test, no new notifications should be sent create_notifications(types=[ NotificationType.DUE_SOON, NotificationType.OVERDUE ], tstamp=datetime.now(timezone.utc)) assert number_of_notifications_sent( loan, notification_type=NotificationType.OVERDUE) == 1 # test overdue notification#3 # For this test, we will update the loan to simulate an overdue of 40 # days. With this delay, regarding the cipo configuration, the second # (and last) overdue reminder should be sent. end_date = datetime.now(timezone.utc) - timedelta(days=40) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) overdue_loans = list(get_overdue_loans()) assert overdue_loans[0].get('pid') == loan_pid create_notifications(types=[ NotificationType.DUE_SOON, NotificationType.OVERDUE ]) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(NotificationType.OVERDUE, 1) assert number_of_notifications_sent( loan, notification_type=NotificationType.OVERDUE) == 2 # checkin the item to put it back to it's original state res, _ = postdata( client, 'api_item.checkin', dict( item_pid=item_pid, pid=loan_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid ) ) assert res.status_code == 200
def test_due_soon_loans(client, librarian_martigny, lib_martigny_data, lib_martigny, patron_martigny, loc_public_martigny, item_type_standard_martigny, item_lib_martigny, circ_policy_short_martigny, yesterday): """Test overdue loans.""" login_user_via_session(client, librarian_martigny.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny.pid can, reasons = item.can_delete assert can assert reasons == {} assert item.available assert not get_last_transaction_loc_for_item(item_pid) assert not item.patron_has_an_active_loan_on_item(patron_martigny) from rero_ils.modules.circ_policies.api import CircPolicy circ_policy = CircPolicy.provide_circ_policy( item.organisation_pid, item.library_pid, patron_martigny.patron_type_pid, item.item_type_pid) circ_policy['reminders'][0]['days_delay'] = 7 circ_policy['checkout_duration'] = 3 circ_policy.update(circ_policy, dbcommit=True, reindex=True) # Remove library exception date to ensure to not been annoyed by # closed dates. custom_lib_data = deepcopy(lib_martigny_data) custom_lib_data['exception_dates'] = [] lib_martigny.update(custom_lib_data, dbcommit=True, reindex=True) flush_index(LibrariesSearch.Meta.index) # checkout res, data = postdata( client, 'api_item.checkout', dict( item_pid=item_pid, patron_pid=patron_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid, )) assert res.status_code == 200 loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') # To be considerate as 'due_soon', we need to update the loan start date # to figure than start_date occurs before due_date. loan = Loan.get_record_by_pid(loan_pid) start_date = ciso8601.parse_datetime(loan.get('start_date')) loan['start_date'] = (start_date - timedelta(days=30)).isoformat() loan.update(loan, dbcommit=True, reindex=True) due_soon_loans = list(get_due_soon_loans()) assert due_soon_loans[0].get('pid') == loan_pid # test due date regarding multiple timezones checkout_loan = Loan.get_record_by_pid(loan_pid) loan_date = ciso8601.parse_datetime(checkout_loan.get('end_date')) # as instance timezone is Europe/Zurich, it should be either 21 or 22 check_timezone_date(pytz.utc, loan_date, [21, 22]) # should be 14:59/15:59 in US/Pacific (because of daylight saving time) check_timezone_date(pytz.timezone('US/Pacific'), loan_date, [14, 15]) check_timezone_date(pytz.timezone('Europe/Amsterdam'), loan_date) # checkin the item to put it back to it's original state res, _ = postdata( client, 'api_item.checkin', dict(item_pid=item_pid, pid=loan_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid)) assert res.status_code == 200 # reset lib lib_martigny.update(lib_martigny_data, dbcommit=True, reindex=True)
def test_notifications_task(client, librarian_martigny, patron_martigny, item_lib_martigny, circ_policy_short_martigny, loc_public_martigny, lib_martigny): """Test overdue and due_soon loans.""" login_user_via_session(client, librarian_martigny.user) item = item_lib_martigny item_pid = item.pid patron_pid = patron_martigny.pid # First we need to create a checkout res, data = postdata( client, 'api_item.checkout', dict( item_pid=item_pid, patron_pid=patron_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid, )) assert res.status_code == 200 loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid') loan = Loan.get_record_by_pid(loan_pid) # test due_soon notification end_date = datetime.now(timezone.utc) + timedelta(days=3) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) due_soon_loans = get_due_soon_loans() assert due_soon_loans[0].get('pid') == loan_pid create_notifications(types=[ Notification.DUE_SOON_NOTIFICATION_TYPE, Notification.OVERDUE_NOTIFICATION_TYPE ]) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(Notification.DUE_SOON_NOTIFICATION_TYPE) # test overdue notification # For this test, we will update the loan to simulate an overdue of 12 # days. With this delay, regarding the cipo configuration, only the first # overdue reminder should be sent. # NOTE : the cipo define the first overdue reminder after 5 days. But we # use an overdue of 12 days because the overdue is based on # loan->item->library open days. Using 12 (5 days + 1 week) we # ensure than the overdue notification will be sent. end_date = datetime.now(timezone.utc) - timedelta(days=12) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) overdue_loans = list(get_overdue_loans()) assert overdue_loans[0].get('pid') == loan_pid create_notifications(types=[ Notification.DUE_SOON_NOTIFICATION_TYPE, Notification.OVERDUE_NOTIFICATION_TYPE ], process=False) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(Notification.OVERDUE_NOTIFICATION_TYPE, 0) assert number_of_reminders_sent( loan, notification_type=Notification.OVERDUE_NOTIFICATION_TYPE) == 1 # test overdue notification#2 # Now simulate than the previous call crashed. So call the task with a # fixed date. In our test, no new notifications should be sent create_notifications(types=[ Notification.DUE_SOON_NOTIFICATION_TYPE, Notification.OVERDUE_NOTIFICATION_TYPE ], tstamp=datetime.now(timezone.utc), process=False) assert number_of_reminders_sent( loan, notification_type=Notification.OVERDUE_NOTIFICATION_TYPE) == 1 # test overdue notification#3 # For this test, we will update the loan to simulate an overdue of 40 # days. With this delay, regarding the cipo configuration, the second # (and last) overdue reminder should be sent. end_date = datetime.now(timezone.utc) - timedelta(days=40) loan['end_date'] = end_date.isoformat() loan.update(loan, dbcommit=True, reindex=True) overdue_loans = list(get_overdue_loans()) assert overdue_loans[0].get('pid') == loan_pid create_notifications(types=[ Notification.DUE_SOON_NOTIFICATION_TYPE, Notification.OVERDUE_NOTIFICATION_TYPE ], process=False) flush_index(NotificationsSearch.Meta.index) flush_index(LoansSearch.Meta.index) assert loan.is_notified(Notification.OVERDUE_NOTIFICATION_TYPE, 1) assert number_of_reminders_sent( loan, notification_type=Notification.OVERDUE_NOTIFICATION_TYPE) == 2 # checkin the item to put it back to it's original state res, _ = postdata( client, 'api_item.checkin', dict(item_pid=item_pid, pid=loan_pid, transaction_location_pid=loc_public_martigny.pid, transaction_user_pid=librarian_martigny.pid)) assert res.status_code == 200