Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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