コード例 #1
0
def test_patron_type_exist_name_and_organisation_pid(patron_type):
    """."""
    ptty = patron_type.replace_refs()
    assert PatronType.exist_name_and_organisation_pid(
        ptty.get('name'),
        ptty.get('organisation', {}).get('pid'))
    assert not PatronType.exist_name_and_organisation_pid(
        'not exists yet',
        ptty.get('organisation', {}).get('pid'))
コード例 #2
0
def test_patron_type_exist_name_and_organisation_pid(
        patron_type_children_martigny):
    """Test patron type name uniquness."""
    org_pid = extracted_data_from_ref(
        patron_type_children_martigny.get('organisation'))
    assert PatronType.exist_name_and_organisation_pid(
        patron_type_children_martigny.get('name'), org_pid)
    assert not PatronType.exist_name_and_organisation_pid(
        'not exists yet', org_pid)
コード例 #3
0
def test_patron_type_exist_name_and_organisation_pid(
        patron_type_children_martigny):
    """Test patron type name uniquness."""
    ptty = patron_type_children_martigny.replace_refs()
    assert PatronType.exist_name_and_organisation_pid(
        ptty.get('name'),
        ptty.get('organisation', {}).get('pid'))
    assert not PatronType.exist_name_and_organisation_pid(
        'not exists yet',
        ptty.get('organisation', {}).get('pid'))
コード例 #4
0
def test_patron_type_es_mapping(es_clear, db, org_martigny,
                                patron_type_children_martigny_data):
    """Test patron types es mapping."""
    search = PatronTypesSearch()
    mapping = get_mapping(search.Meta.index)
    assert mapping
    PatronType.create(patron_type_children_martigny_data,
                      dbcommit=True,
                      reindex=True,
                      delete_pid=True)
    assert mapping == get_mapping(search.Meta.index)
コード例 #5
0
def test_patron_type_es_mapping(es_clear, db, organisation,
                                patron_type_data_tmp):
    """."""
    search = PatronTypesSearch()
    mapping = get_mapping(search.Meta.index)
    assert mapping
    PatronType.create(patron_type_data_tmp,
                      dbcommit=True,
                      reindex=True,
                      delete_pid=True)
    assert mapping == get_mapping(search.Meta.index)
コード例 #6
0
def test_patron_type_create(db, patron_type_data_tmp):
    """Test pttyanisation creation."""
    ptty = PatronType.create(patron_type_data_tmp, delete_pid=True)
    assert ptty == patron_type_data_tmp
    assert ptty.get('pid') == '1'

    ptty = PatronType.get_record_by_pid('1')
    assert ptty == patron_type_data_tmp

    fetched_pid = patron_type_id_fetcher(ptty.id, ptty)
    assert fetched_pid.pid_value == '1'
    assert fetched_pid.pid_type == 'ptty'
コード例 #7
0
def test_patron_type_create(db, org_martigny,
                            patron_type_children_martigny_data):
    """Test pttyanisation creation."""
    ptty = PatronType.create(patron_type_children_martigny_data,
                             delete_pid=True)
    assert ptty == patron_type_children_martigny_data
    assert ptty.get('pid') == '1'

    ptty = PatronType.get_record_by_pid('1')
    assert ptty == patron_type_children_martigny_data

    fetched_pid = patron_type_id_fetcher(ptty.id, ptty)
    assert fetched_pid.pid_value == '1'
    assert fetched_pid.pid_type == 'ptty'
コード例 #8
0
def test_patron_types_subscription(patron_type_youngsters_sion,
                                   patron_type_adults_martigny,
                                   patron_type_grown_sion, patron_sion):
    """Test subscription behavior for patron_types."""
    assert patron_type_youngsters_sion.is_subscription_required

    # A patron_type with a subscription amount equal to 0 doesn't
    # require a subscription
    patron_type_youngsters_sion['subscription_amount'] = 0
    assert not patron_type_youngsters_sion.is_subscription_required

    del (patron_type_youngsters_sion['subscription_amount'])
    assert not patron_type_youngsters_sion.is_subscription_required

    # Test the 'get_yearly_subscription_patron_types' function.
    assert len(list(PatronType.get_yearly_subscription_patron_types())) == 2

    # Test 'get_linked_patrons' functions.
    assert len(list(patron_type_grown_sion.get_linked_patron())) == 1
    assert len(list(patron_type_youngsters_sion.get_linked_patron())) == 0
    patron_sion['patron']['type']['$ref'] = get_ref_for_pid(
        'ptty', patron_type_youngsters_sion.pid)
    patron_sion.update(patron_sion, dbcommit=True)
    patron_sion.reindex()
    assert len(list(patron_type_grown_sion.get_linked_patron())) == 0
    assert len(list(patron_type_youngsters_sion.get_linked_patron())) == 1
    patron_sion['patron']['type']['$ref'] = get_ref_for_pid(
        'ptty', patron_type_grown_sion.pid)
    patron_sion.update(patron_sion, dbcommit=True)
    patron_sion.reindex()
コード例 #9
0
ファイル: organisations.py プロジェクト: zannkukai/rero-ils
def patron_type_tmp(db, org_martigny, patron_type_children_martigny_data):
    """Create scope function children patron type of martigny."""
    ptty = PatronType.create(
        data=patron_type_children_martigny_data,
        dbcommit=True,
        delete_pid=True)
    return ptty
コード例 #10
0
ファイル: organisations.py プロジェクト: rerowep/rero-ils
def patron_type_grown_sion(app, org_sion, patron_type_grown_sion_data):
    """Crate grown patron type of sion."""
    ptty = PatronType.create(data=patron_type_grown_sion_data,
                             delete_pid=False,
                             dbcommit=True,
                             reindex=True)
    flush_index(PatronTypesSearch.Meta.index)
    return ptty
コード例 #11
0
def patron_type(app, organisation, patron_type_data):
    """."""
    ptty = PatronType.create(data=patron_type_data,
                             delete_pid=False,
                             dbcommit=True,
                             reindex=True)
    flush_index(PatronTypesSearch.Meta.index)
    return ptty
コード例 #12
0
ファイル: organisations.py プロジェクト: rerowep/rero-ils
def patron_type_adults_martigny(app, org_martigny,
                                patron_type_adults_martigny_data):
    """Create adults patron type of martigny."""
    ptty = PatronType.create(data=patron_type_adults_martigny_data,
                             delete_pid=False,
                             dbcommit=True,
                             reindex=True)
    flush_index(PatronTypesSearch.Meta.index)
    return ptty
コード例 #13
0
def downgrade():
    """Downgrade patron type records."""
    query = PatronTypesSearch()\
        .filter('exists', field='limits.unpaid_subscription')\
        .source('pid')
    patron_type_pids = [hit.pid for hit in query.scan()]
    ids = []
    for pid in patron_type_pids:
        record = PatronType.get_record_by_pid(pid)
        del record['limits']['unpaid_subscription']
        record.update(record, dbcommit=True, reindex=False)
        LOGGER.info(f'  * Updated PatronType#{record.pid}')
        ids.append(record.id)
    _indexing_records(ids)
    LOGGER.info(f'TOTAL :: {len(ids)}')
コード例 #14
0
def upgrade():
    """Upgrade patron type records.

    We need to add the new 'unpaid_subscription' limit to each patron_type
    records that doesn't already define such a limit. The default value for
    existing records will be `False` to not generate unstable behavior.
    """
    query = PatronTypesSearch()\
        .exclude('exists', field='limits.unpaid_subscription')\
        .source('pid')
    patron_type_pids = [hit.pid for hit in query.scan()]
    ids = []
    for pid in patron_type_pids:
        record = PatronType.get_record_by_pid(pid)
        record\
            .setdefault('limits', {})\
            .setdefault('unpaid_subscription', False)
        record.update(record, dbcommit=True, reindex=False)
        LOGGER.info(f'  * Updated PatronType#{record.pid}')
        ids.append(record.id)
    _indexing_records(ids)
    LOGGER.info(f'TOTAL :: {len(ids)}')
コード例 #15
0
ファイル: tasks.py プロジェクト: blankoworld/rero-ils
def check_patron_types_and_add_subscriptions():
    """Check patron types with subscription amount and add subscriptions.

    Search for patron_type requiring a subscription. For each patron_type
    search about patron linked to it and without valid subscription. For
    each of these patrons, create a new subscription if needed.
    """
    # Note this function should never doing anything because never any patron
    # linked to these patron types shouldn't have no subscription. This is
    # because, a listener creating an active subscription is linked to signal
    # create/update for any patron. In addition, the method
    # `clean_obsolete_subscriptions`, at the last loop instruction will update
    # the patron ; this update will trigger the same listener and so create a
    # new active subscription is necessary.
    for ptty in PatronType.get_yearly_subscription_patron_types():
        patron_no_subsc = Patron.get_patrons_without_subscription(ptty.pid)
        for patron in patron_no_subsc:
            msg = 'Add a subscription for patron#{ptrn} ... it shouldn\'t ' \
                  'happen !!'.format(ptrn=patron.pid)
            current_app.logger.error(msg)
            start_date = datetime.now()
            end_date = add_years(start_date, 1)
            patron.add_subscription(ptty, start_date, end_date)
コード例 #16
0
ファイル: conftest.py プロジェクト: cs-ubr/rero-ils
def tmp_patron_type(db, patron_type_data):
    """."""
    org = PatronType.create(data=patron_type_data,
                            dbcommit=True,
                            delete_pid=True)
    return org
コード例 #17
0
def test_checkout_library_limit(client, app, librarian_martigny, lib_martigny,
                                patron_type_children_martigny,
                                item_lib_martigny, item2_lib_martigny,
                                item3_lib_martigny, item_lib_martigny_data,
                                item2_lib_martigny_data,
                                item3_lib_martigny_data, loc_public_martigny,
                                patron_martigny, circ_policy_short_martigny):
    """Test checkout library limits."""

    patron = patron_martigny
    item2_original_data = deepcopy(item2_lib_martigny_data)
    item3_original_data = deepcopy(item3_lib_martigny_data)
    item1 = item_lib_martigny
    item2 = item2_lib_martigny
    item3 = item3_lib_martigny
    library_ref = get_ref_for_pid('lib', lib_martigny.pid)
    location_ref = get_ref_for_pid('loc', loc_public_martigny.pid)

    login_user_via_session(client, librarian_martigny.user)

    # Update fixtures for the tests
    #   * Update the patron_type to set a checkout limits
    #   * All items are linked to the same library/location
    patron_type = patron_type_children_martigny
    patron_type['limits'] = {
        'checkout_limits': {
            'global_limit': 3,
            'library_limit': 2,
            'library_exceptions': [{
                'library': {
                    '$ref': library_ref
                },
                'value': 1
            }]
        }
    }
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    item2_lib_martigny_data['location']['$ref'] = location_ref
    item2.update(item2_lib_martigny_data, dbcommit=True, reindex=True)
    item3_lib_martigny_data['location']['$ref'] = location_ref
    item3.update(item3_lib_martigny_data, dbcommit=True, reindex=True)

    # First checkout - All should be fine.
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item1.pid,
            patron_pid=patron.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200
    loan1_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')

    # Second checkout
    #   --> The library limit exception should be raised.
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item2.pid,
            patron_pid=patron.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']

    # remove the library specific exception and try a new checkout
    #   --> As the limit by library is now '2', the checkout will be done.
    #   --> Try a third checkout : the default library_limit exception should
    #       be raised
    patron_type['limits'] = {
        'checkout_limits': {
            'global_limit': 3,
            'library_limit': 2,
        }
    }
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item2.pid,
            patron_pid=patron.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200
    loan2_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item3.pid,
            patron_pid=patron.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']

    # remove the library default limit and update the global_limit to 2.
    #   --> try the third checkout : the global_limit exception should now be
    #       raised
    patron_type['limits'] = {'checkout_limits': {'global_limit': 2}}
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item3.pid,
            patron_pid=patron.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']

    # check the circulation information API
    url = url_for('api_patrons.patron_circulation_informations',
                  patron_pid=patron.pid)
    res = client.get(url)
    assert res.status_code == 200
    data = get_json(res)
    assert 'error' == data['messages'][0]['type']
    assert 'Checkout denied' in data['messages'][0]['content']

    # try a checkout with 'override_blocking' parameter.
    #   --> the restriction is no longer checked, the checkout will be success.
    res, data = postdata(client,
                         'api_item.checkout',
                         dict(
                             item_pid=item3.pid,
                             patron_pid=patron.pid,
                             transaction_location_pid=loc_public_martigny.pid,
                             transaction_user_pid=librarian_martigny.pid,
                         ),
                         url_data={'override_blocking': 'true'})
    assert res.status_code == 200
    loan3_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')

    # reset fixtures
    #   --> checkin three loaned item
    #   --> reset patron_type to original value
    #   --> reset items to original values
    res, data = postdata(
        client, 'api_item.checkin',
        dict(
            item_pid=item3.pid,
            pid=loan3_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    res, data = postdata(
        client, 'api_item.checkin',
        dict(
            item_pid=item2.pid,
            pid=loan2_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200
    res, data = postdata(
        client, 'api_item.checkin',
        dict(
            item_pid=item1.pid,
            pid=loan1_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 200
    del patron_type['limits']
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    item2.update(item2_original_data, dbcommit=True, reindex=True)
    item3.update(item3_original_data, dbcommit=True, reindex=True)
コード例 #18
0
def test_overdue_limit(client, app, librarian_martigny, lib_martigny,
                       item_lib_martigny, item2_lib_martigny,
                       patron_type_children_martigny, item3_lib_martigny,
                       item_lib_martigny_data, item2_lib_martigny_data,
                       item3_lib_martigny_data, loc_public_martigny,
                       patron_martigny, circ_policy_short_martigny):
    """Test overdue limit."""

    item = item_lib_martigny
    item_pid = item.pid
    patron_pid = patron_martigny.pid

    # [0] prepare overdue transaction
    login_user_via_session(client, librarian_martigny.user)
    # 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)
    assert not loan.is_loan_overdue()
    end_date = datetime.now(timezone.utc) - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)

    overdue_loans = list(get_overdue_loans(patron_pid=patron_pid))
    assert loan.is_loan_overdue()
    assert loan.end_date == end_date.isoformat()
    assert overdue_loans[0].get('pid') == loan_pid
    assert number_of_reminders_sent(loan) == 0

    loan.create_notification(
        notification_type=Notification.OVERDUE_NOTIFICATION_TYPE)
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert number_of_reminders_sent(loan) == 1

    # [1] test overdue items limit

    # Update the patron_type to set a overdue_items_limit rule
    patron_type = patron_type_children_martigny
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('overdue_items_limits', {}) \
        .setdefault('default_value', 1)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits', {}).get('overdue_items_limits', {})\
        .get('default_value') == 1

    # [1.1] test overdue items limit when we try to checkout a second item
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']

    # [1.2] test overdue items limit when we try to request another item
    res, data = postdata(
        client, 'api_item.librarian_request',
        dict(item_pid=item2_lib_martigny.pid,
             patron_pid=patron_pid,
             pickup_location_pid=loc_public_martigny.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']

    # [1.3] test overdue items limit when we try to extend loan
    res, _ = postdata(
        client, 'api_item.extend_loan',
        dict(item_pid=item_pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))

    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']

    # [2] test fee amount limit

    # Update the patron_type to set a fee_amount_limit rule
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('fee_amount_limits', {}) \
        .setdefault('default_value', 0.5)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits', {}).get('fee_amount_limits', {}) \
        .get('default_value') == 0.5

    # [2.1] test fee amount limit when we try to checkout a second item
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        ))
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.2] test fee amount limit when we try to request another item
    res, data = postdata(
        client, 'api_item.librarian_request',
        dict(item_pid=item2_lib_martigny.pid,
             patron_pid=patron_pid,
             pickup_location_pid=loc_public_martigny.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.3] test fee amount limit when we try to extend loan
    res, _ = postdata(
        client, 'api_item.extend_loan',
        dict(item_pid=item_pid,
             transaction_user_pid=librarian_martigny.pid,
             transaction_location_pid=loc_public_martigny.pid))

    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits') is None

    # # 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
コード例 #19
0
def test_overdue_limit(
     client, app, librarian_martigny, lib_martigny, item_lib_martigny,
     item2_lib_martigny, patron_type_children_martigny,
     item3_lib_martigny, item_lib_martigny_data, item2_lib_martigny_data,
     item3_lib_martigny_data, loc_public_martigny, patron_martigny,
     circ_policy_short_martigny, lib_saxon, loc_public_saxon):
    """Test overdue limit."""

    item = item_lib_martigny
    item_pid = item.pid
    patron_pid = patron_martigny.pid
    date_format = '%Y/%m/%dT%H:%M:%S.000Z'
    today = datetime.utcnow()
    eod = today.replace(hour=23, minute=59, second=0, microsecond=0,
                        tzinfo=lib_martigny.get_timezone())

    # STEP 0 :: Prepare data for test
    #   * Update the patron_type to set a overdue_items_limit rule.
    #     We define than only 1 overdue items are allowed. Trying a second
    #     checkout is disallowed if patron has an overdue item
    patron_type = patron_type_children_martigny
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('overdue_items_limits', {}) \
        .setdefault('default_value', 1)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type\
        .get('limits', {})\
        .get('overdue_items_limits', {}) \
        .get('default_value') == 1

    # STEP 1 :: Create an checkout with a end_date at the current date
    #   * Create a checkout and set end_date to a fixed_date equals to
    #     current tested date. The loan should not be considered as overdue
    #     and a second checkout should be possible
    login_user_via_session(client, librarian_martigny.user)
    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,
            end_date=eod.strftime(date_format)
        )
    )
    assert res.status_code == 200
    loan_pid = data.get('action_applied')[LoanAction.CHECKOUT].get('pid')
    loan = Loan.get_record_by_pid(loan_pid)
    assert not loan.is_loan_overdue()

    tmp_item_data = deepcopy(item_lib_martigny_data)
    del tmp_item_data['pid']
    tmp_item_data['library']['$ref'] = get_ref_for_pid('lib', lib_saxon.pid)
    tmp_item_data['location']['$ref'] = \
        get_ref_for_pid('loc', loc_public_saxon.pid)
    tmp_item = Item.create(tmp_item_data, dbcommit=True, reindex=True)
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=tmp_item.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200
    res, _ = postdata(
        client,
        'api_item.checkin',
        dict(
            item_pid=tmp_item.pid,
            pid=data.get('action_applied')[LoanAction.CHECKOUT].get('pid'),
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200

    # STEP 2 :: Set the loan as overdue and test a new checkout
    #   Now there is one loan in overdue, then the limit is reached and a new
    #   checkout shouldn't be possible
    end_date = eod - timedelta(days=7)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)

    overdue_loans = list(get_overdue_loans(patron_pid=patron_pid))
    assert loan.is_loan_overdue()
    assert loan.end_date == end_date.isoformat()
    assert overdue_loans[0].get('pid') == loan_pid
    assert not get_notification(loan, NotificationType.OVERDUE)

    notification = loan.create_notification(
        _type=NotificationType.OVERDUE).pop()
    Dispatcher.dispatch_notifications([notification.get('pid')])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)
    assert get_notification(loan, NotificationType.OVERDUE)

    # Try a second checkout - limit should be reached
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 403
    assert 'Checkout denied' in data['message']
    # Try a request - limit should be reached
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            pickup_location_pid=loc_public_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']
    # Try to extend - limit should be reached
    res, _ = postdata(
        client,
        'api_item.extend_loan',
        dict(
            item_pid=item_pid,
            transaction_user_pid=librarian_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal number of overdue items is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']

    # [2] test fee amount limit

    # Update the patron_type to set a fee_amount_limit rule
    patron_type \
        .setdefault('limits', {}) \
        .setdefault('fee_amount_limits', {}) \
        .setdefault('default_value', 0.5)
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits', {}).get('fee_amount_limits', {}) \
        .get('default_value') == 0.5

    # [2.1] test fee amount limit when we try to checkout a second item
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.2] test fee amount limit when we try to request another item
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item2_lib_martigny.pid,
            patron_pid=patron_pid,
            pickup_location_pid=loc_public_martigny.pid,
            transaction_library_pid=lib_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # [2.3] test fee amount limit when we try to extend loan
    res, _ = postdata(
        client,
        'api_item.extend_loan',
        dict(
            item_pid=item_pid,
            transaction_user_pid=librarian_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid
        )
    )

    assert res.status_code == 403
    assert 'maximal overdue fee amount is reached' in data['message']

    # reset the patron_type with default value
    del patron_type['limits']
    patron_type.update(patron_type, dbcommit=True, reindex=True)
    patron_type = PatronType.get_record_by_pid(patron_type.pid)
    assert patron_type.get('limits') is None

    # # 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