def test_add_request_on_item_at_desk(
        client, item_at_desk_martigny_patron_and_loan_at_desk,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test requests on an at_desk item."""
    item, patron, loan = item_at_desk_martigny_patron_and_loan_at_desk

    # the following tests the circulation action ADD_REQUEST_2_1
    # the patron owns the ITEM_AT_DESK loan may not create a new pending loan.
    params = {
        'patron_pid': patron.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    with pytest.raises(RecordCannotBeRequestedError):
        item, loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING,
            params=params, copy_item=False)

    # the following tests the circulation action ADD_REQUEST_2_2
    # a patron who doesnt own the ITEM_AT_DESK loan can add a new pending loan.

    params['patron_pid'] = patron2_martigny_no_email.pid
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    assert loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 2
0
def test_operation_logs_serializers(client, rero_json_header, patron_martigny,
                                    librarian_martigny, item_lib_martigny,
                                    loc_public_martigny, circulation_policies,
                                    lib_martigny_data):
    """Test serializers for operation logs."""
    login_user(client, patron_martigny)
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid,
    }
    item_record_to_a_specific_loan_state(item=item_lib_martigny,
                                         loan_state=LoanState.ITEM_AT_DESK,
                                         params=params,
                                         copy_item=True)
    # Force update ES index
    flush_index(OperationLogsSearch.Meta.index)
    list_url = url_for('invenio_records_rest.oplg_list')
    response = client.get(list_url, headers=rero_json_header)
    assert response.status_code == 200
    data = get_json(response)
    loan = data.get('hits', {}).get('hits', [])[0].get('metadata', {})\
        .get('loan', {})
    libary_name = lib_martigny_data['name']
    # Check if the library data injected into the section
    assert libary_name == loan.get('transaction_location', {})\
        .get('library').get('name')
    assert libary_name == loan.get('pickup_location', {})\
        .get('library').get('name')
def test_add_request_on_item_on_shelf(
        item_on_shelf_martigny_patron_and_loan_pending,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test requests on an on_shelf item."""
    item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending

    # the following tests the circulation action ADD_REQUEST_1_1
    # an on_shelf item with no pending requests can have new pending requests.
    assert loan['state'] == LoanState.PENDING

    # the following tests the circulation action ADD_REQUEST_1_2_1
    # for an item on_shelf with a pending loan, the patron that owns the
    # pending loan can not add a new pending loan on same item.
    params = {
        'patron_pid': patron.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    with pytest.raises(RecordCannotBeRequestedError):
        item, loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING, params=params,
            copy_item=False)

    # the following tests the circulation action ADD_REQUEST_1_2_2
    # for an item on_shelf with a pending loan, a patron that does not own the
    # pending loan can add a new pending loan on same item.
    params['patron_pid'] = patron2_martigny_no_email.pid
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING,
        params=params, copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
def test_add_request_on_item_in_transit_for_pickup(
        item_in_transit_martigny_patron_and_loan_for_pickup,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, loc_public_fully):
    """Test requests on an in_transit item for pickup."""
    item, patron, loan = item_in_transit_martigny_patron_and_loan_for_pickup

    # the following tests the circulation action ADD_REQUEST_4_1
    # the owner of the IN_TRANSIT_FOR_PICKUP loan can not add a pending loan
    params = {
        'patron_pid': patron.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_fully.pid
    }
    with pytest.raises(RecordCannotBeRequestedError):
        item, loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING,
            params=params, copy_item=False)

    # the following tests the circulation action ADD_REQUEST_4_2
    # a patron who does not own the IN_TRANSIT_FOR_PICKUP loan can add
    # a new pending loan.
    params['patron_pid'] = patron2_martigny_no_email.pid
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def test_checkin_on_item_on_loan_with_requests_externally(
        item4_on_loan_martigny_patron_and_loan_on_loan,
        item5_on_loan_martigny_patron_and_loan_on_loan,
        loc_public_martigny, librarian_martigny,
        patron2_martigny, loc_public_fully, loc_public_saxon):
    """Test checkin on an on_loan item with requests at an external library."""
    item, patron, loan = item4_on_loan_martigny_patron_and_loan_on_loan
    # the following tests the circulation action CHECKIN_3_2_2_1
    # for an item on_loan, with pending requests. when the pickup library of
    # the first pending request does not equal to the transaction library,
    # checkin the item and the loan on_loan is cancelled.
    # if the pickup location of the first pending equal the item library,
    # the pending loan becomes ITEM_IN_TRANSIT_FOR_PICKUP

    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_fully.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }

    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)

    item, actions = item.checkin(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.CANCELLED
    assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP

    item, patron, loan = item5_on_loan_martigny_patron_and_loan_on_loan
    # the following tests the circulation action CHECKIN_3_2_2_2
    # for an item on_loan, with pending requests. when the pickup library of
    # the first pending request does not equal to the transaction library,
    # checkin the item and the loan on_loan is cancelled.
    # if the pickup location of the first pending does not equal the item
    # library, the pending loan becomes ITEM_IN_TRANSIT_FOR_PICKUP

    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_saxon.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_fully.pid
    }

    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)

    item, actions = item.checkin(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.CANCELLED
    assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
def test_add_request_on_item_on_loan(
        item_on_loan_martigny_patron_and_loan_on_loan,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, patron4_martigny_no_email):
    """Test requests on an on_loan item."""
    item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan

    # the following tests the circulation action ADD_REQUEST_3_1
    # the patron owns the ITEM_ON_LOAN loan may not create a new pending loan.
    params = {
        'patron_pid': patron.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    with pytest.raises(RecordCannotBeRequestedError):
        item, loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING,
            params=params, copy_item=False)

    # the following tests the circulation action ADD_REQUEST_3_2_1
    # any patron who does not own the ITEM_ON_LOAN loan can add a new pending
    # loan.
    params['patron_pid'] = patron2_martigny_no_email.pid
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    assert loan['state'] == LoanState.ITEM_ON_LOAN

    # the following tests the circulation action ADD_REQUEST_3_2_2_1
    # when an item on_loan has pending requests, the patron who owns the
    # pending loan may not add a new pending loan
    params['patron_pid'] = patron2_martigny_no_email.pid
    with pytest.raises(RecordCannotBeRequestedError):
        item, requested_loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING, params=params,
            copy_item=False)
    # the following tests the circulation action ADD_REQUEST_3_2_2_2
    # when an item on_loan has pending requests, any patron who does not own
    # the pending loan may add a new pending loan
    params['patron_pid'] = patron4_martigny_no_email.pid
    item, second_requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert loan['state'] == LoanState.ITEM_ON_LOAN
    assert requested_loan['state'] == LoanState.PENDING
    assert second_requested_loan['state'] == LoanState.PENDING
def test_cancel_request_on_item_at_desk_with_requests_externally(
        client, item3_at_desk_martigny_patron_and_loan_at_desk,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, loc_public_fully):
    """Test cancel requests on an at_desk item with requests at externally."""
    item, patron, loan = item3_at_desk_martigny_patron_and_loan_at_desk
    # the following tests the circulation action CANCEL_REQUEST_2_1_2_1
    # an item at_desk with other pending loans.
    # pickup location of 1st pending loan != pickup location of current loan
    # cancel the current loan and item is: in_transit, automatic validation of
    # firt pending loan

    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_fully.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_fully.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING,
        params=params, copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.CANCELLED
    assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
Esempio n. 8
0
def item_on_shelf_fully_patron_and_loan_pending(
    app,
    librarian_martigny,
    item_lib_fully,
    loc_public_fully,
    patron_martigny,
    circulation_policies,
):
    """Creates an item on_shelf requested by a patron.

    :return item: the created or copied item.
    :return patron: the patron placed the request.
    :return loan: the pending loan.
    """
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_fully.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_fully.pid
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_fully,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=True)
    return item, patron_martigny, loan
Esempio n. 9
0
def test_checkin_on_item_on_loan_with_requests(
        item3_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny,
        librarian_martigny_no_email, patron2_martigny_no_email):
    """Test checkin on an on_loan item with requests at local library."""
    item, patron, loan = item3_on_loan_martigny_patron_and_loan_on_loan
    # the following tests the circulation action CHECKIN_3_2_1
    # for an item on_loan, with pending requests. when the pickup library of
    # the first pending request equal to the transaction library,
    # checkin the item and item becomes at_desk.
    # the on_loan is returned and validating the first pending loan request.
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }

    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)

    item, actions = item.checkin(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.AT_DESK
    assert loan['state'] == LoanState.ITEM_RETURNED
    assert requested_loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 10
0
def test_cancel_pending_on_item_in_transit_to_house(
        client, item3_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test cancel pending loan on an in_transit to_house item."""
    item, patron, loan = item3_in_transit_martigny_patron_and_loan_to_house
    # the following tests the circulation action CANCEL_REQUEST_5_2
    # an item in_transit to house with pending loans. when a
    # librarian wants to cancel the pending loan. action is permitted.
    # the loan will be cancelled. and in_transit loan remains in transit.
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': requested_loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    assert requested_loan['state'] == LoanState.CANCELLED
Esempio n. 11
0
def test_cancel_request_on_item_in_transit_to_house_with_requests(
        client, item2_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny, patron2_martigny):
    """Test cancel request on an in_transit to_house item with requests."""
    item, patron, loan = item2_in_transit_martigny_patron_and_loan_to_house
    # the following tests the circulation action CANCEL_REQUEST_5_1_2
    # an item in_transit to house with pending loans. when a
    # librarian wants to cancel the in_transit loan. action is permitted.
    # the loan will be cancelled. and first pending loan will be validated.
    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.AT_DESK
    assert loan['state'] == LoanState.CANCELLED
    assert requested_loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 12
0
def test_cancel_pending_loan_on_item_in_transit_for_pickup_with_requests(
        client, item3_in_transit_martigny_patron_and_loan_for_pickup,
        loc_public_martigny, librarian_martigny, patron2_martigny):
    """Test cancel pending loan on an in_transit for pickup item."""
    item, patron, loan = item3_in_transit_martigny_patron_and_loan_for_pickup
    # the following tests the circulation action CANCEL_REQUEST_4_2
    # an item in_transit for pickup with other pending loans. when a
    # librarian wants to cancel the pending loan. action is permitted.
    # item remains in_transit

    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': requested_loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
    assert requested_loan['state'] == LoanState.CANCELLED
Esempio n. 13
0
def test_cancel_pending_request_on_item_at_desk(
        client, item5_at_desk_martigny_patron_and_loan_at_desk,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test cancel requests on an at_desk item with requests at home."""
    item, patron, loan = item5_at_desk_martigny_patron_and_loan_at_desk
    # the following tests the circulation action CANCEL_REQUEST_2_2
    # an item at_desk with other pending loans. when a librarian wants to
    # cancel one of the pending loans. the item remains at_desk
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': requested_loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.AT_DESK
    assert requested_loan['state'] == LoanState.CANCELLED
    assert loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 14
0
def test_checkin_on_item_on_shelf_with_requests(
        item_on_shelf_martigny_patron_and_loan_pending,
        loc_public_martigny, librarian_martigny,
        patron2_martigny, loc_public_fully, lib_martigny):
    """Test checkin on an on_shelf item with requests."""
    item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending
    # the following tests the circulation action CHECKIN_1_2_1
    # for an item on_shelf with pending loans, the pickup library of the first
    # pending loan equal to the transaction library, the first pending loan
    # is validated and item assigned the at_desk
    # validate_request circulation action will be performed.

    # create a second pending loan on same item
    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_fully.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING,
        params=params, copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid
    }
    item, actions = item.checkin(**params)
    assert item.status == ItemStatus.AT_DESK
    assert Loan.get_record_by_pid(loan.pid)['state'] == LoanState.ITEM_AT_DESK
    assert Loan.get_record_by_pid(requested_loan.pid)['state'] == \
        LoanState.PENDING
def test_validate_on_item_at_desk(
        item_at_desk_martigny_patron_and_loan_at_desk,
        loc_public_martigny, librarian_martigny,
        circulation_policies, patron2_martigny):
    """Test validate a request on an item at_desk."""
    # the following tests the circulation action VALIDATE_2
    # on at_desk item, the validation is not possible
    item, patron, loan = item_at_desk_martigny_patron_and_loan_at_desk
    params = {
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pid': loan.pid
    }
    with pytest.raises(NoValidTransitionAvailableError):
        item, actions = item.validate_request(**params)
    assert item.status == ItemStatus.AT_DESK
    loan = Loan.get_record_by_pid(loan.pid)
    assert loan['state'] == LoanState.ITEM_AT_DESK
    # will not be able to validate any requestes for this item
    params = {
        'patron_pid': patron2_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    params['pid'] = requested_loan.pid
    with pytest.raises(NoValidTransitionAvailableError):
        item, actions = item.validate_request(**params)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert requested_loan['state'] == LoanState.PENDING
    loan = Loan.get_record_by_pid(loan.pid)
    assert loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 16
0
def test_loans_serializers(client, rero_json_header, patron_martigny,
                           loc_public_martigny, loc_public_fully,
                           librarian_martigny, item_lib_martigny,
                           item_lib_fully, circulation_policies):
    """Test serializers for loans."""
    login_user(client, patron_martigny)
    # create somes loans on same item with different state
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid,
    }
    item_record_to_a_specific_loan_state(item=item_lib_martigny,
                                         loan_state=LoanState.PENDING,
                                         params=params,
                                         copy_item=True)
    item_record_to_a_specific_loan_state(item=item_lib_martigny,
                                         loan_state=LoanState.ITEM_AT_DESK,
                                         params=params,
                                         copy_item=True)
    item_record_to_a_specific_loan_state(item=item_lib_martigny,
                                         loan_state=LoanState.ITEM_ON_LOAN,
                                         params=params,
                                         copy_item=True)
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_fully.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_fully.pid,
        'checkin_transaction_location_pid': loc_public_martigny.pid
    }
    item_record_to_a_specific_loan_state(
        item=item_lib_fully,
        loan_state=LoanState.ITEM_IN_TRANSIT_TO_HOUSE,
        params=params,
        copy_item=True)

    list_url = url_for('invenio_records_rest.loanid_list')
    response = client.get(list_url, headers=rero_json_header)
    assert response.status_code == 200
    data = get_json(response)
    records = data.get('hits', {}).get('hits', [])
    for record in records:
        data = record.get('metadata', {})
        if data.get('state') == 'PENDING':
            assert data.get('pickup_name')
        elif data.get('state') == 'ITEM_AT_DESK':
            assert data.get('rank') == 0
        elif data.get('state') == 'ITEM_ON_LOAN':
            assert data.get('overdue') is False
        elif data.get('state') == 'ITEM_IN_TRANSIT_TO_HOUSE':
            assert data.get('pickup_library_name')
            assert data.get('transaction_library_name')
def test_add_request_on_item_in_transit_to_house(
        item_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, loc_public_fully,
        patron4_martigny_no_email):
    """Test requests on an in_transit item to house."""
    item, patron, loan = item_in_transit_martigny_patron_and_loan_to_house

    # the following tests the circulation action ADD_REQUEST_5_1
    # any patron can add a new pending loan on an item with a loan equal to
    # ITEM_IN_TRANSIT_TO_HOUSE
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid,
        'checkin_transaction_location_pid': loc_public_fully.pid,
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    assert requested_loan['state'] == LoanState.PENDING

    # the following tests the circulation action ADD_REQUEST_5_2_1
    # when a pending loan exist on an item with loan ITEM_IN_TRANSIT_TO_HOUSE
    # the patron who owns the pending loan can not add a new pending loan
    with pytest.raises(RecordCannotBeRequestedError):
        item, requested_loan = item_record_to_a_specific_loan_state(
            item=item, loan_state=LoanState.PENDING, params=params,
            copy_item=False)

    # the following tests the circulation action ADD_REQUEST_5_2_2
    # when a pending loan exist on an item with loan ITEM_IN_TRANSIT_TO_HOUSE,
    # any patron who does now own the pending loan can add a new pending loan.
    params['patron_pid'] = patron4_martigny_no_email.pid
    item, second_requested_loan = item_record_to_a_specific_loan_state(
        item=item, loan_state=LoanState.PENDING, params=params,
        copy_item=False)
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    assert second_requested_loan['state'] == LoanState.PENDING
    assert requested_loan['state'] == LoanState.PENDING
Esempio n. 18
0
def test_cancel_item_request_on_item_on_loan(
        client, item_on_loan_martigny_patron_and_loan_on_loan,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test cancel requests on an on_loan item."""
    item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan
    # the following tests the circulation action CANCEL_REQUEST_3_1
    # an item on_loan with no other pending loans. when a librarian wants to
    # cancel the on_loan loan. action is not permitted and item remain on_loan.
    params = {
        'pid': loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    with pytest.raises(NoCirculationAction):
        item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    assert item.status == ItemStatus.ON_LOAN
    assert loan['state'] == LoanState.ITEM_ON_LOAN

    # the following tests the circulation action CANCEL_REQUEST_3_2
    # an item on_loan with other pending loans. when a librarian wants to
    # cancel the pending loan. action is permitted and item remains on_loan.
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING

    params = {
        'pid': requested_loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.ON_LOAN
    assert loan['state'] == LoanState.ITEM_ON_LOAN
    assert requested_loan['state'] == LoanState.CANCELLED
Esempio n. 19
0
def item3_in_transit_martigny_patron_and_loan_for_pickup(
        app, librarian_martigny, loc_public_fully, item_lib_martigny,
        loc_public_martigny, patron_martigny, circulation_policies):
    """Creates an item in_transit for pickup.

    :return item: the created or copied item.
    :return patron: the patron requested the item.
    :return loan: the ITEM_IN_TRANSIT_FOR_PICKUP loan.
    """
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_fully.pid
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny,
        loan_state=LoanState.ITEM_IN_TRANSIT_FOR_PICKUP,
        params=params,
        copy_item=True)
    return item, patron_martigny, loan
Esempio n. 20
0
def item6_in_transit_martigny_patron_and_loan_to_house(
        app, librarian_martigny, loc_public_fully, item_lib_martigny,
        loc_public_martigny, patron_martigny, circulation_policies):
    """Creates an item in_transit to house.

    :return item: the created or copied item.
    :return patron: the patron who returned this item.
    :return loan: the ITEM_IN_TRANSIT_TO_HOUSE loan.
    """
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid,
        'checkin_transaction_location_pid': loc_public_fully.pid,
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny,
        loan_state=LoanState.ITEM_IN_TRANSIT_TO_HOUSE,
        params=params,
        copy_item=True)
    return item, patron_martigny, loan
Esempio n. 21
0
def test_checkin_on_item_in_transit_to_house_with_requests(
        item3_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test checkin on an in_transit item to house."""
    item, patron, loan = item3_in_transit_martigny_patron_and_loan_to_house

    # the following tests the circulation action CHECKIN_5_2_1_1
    # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests.
    # when the pickup library of the first pending request does equal to the
    # transaction library. and pickup library equals to the item library,
    # will receive the item.
    # the item becomes at_desk and the loan is terminated.
    # and will validate the first pending loan
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    params = {
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }

    item, actions = item.checkin(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.AT_DESK
    assert loan['state'] == LoanState.ITEM_RETURNED
    assert requested_loan['state'] == LoanState.ITEM_AT_DESK
Esempio n. 22
0
def test_cancel_request_on_item_on_shelf(
        item_lib_martigny, item_on_shelf_martigny_patron_and_loan_pending,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email):
    """Test cancel request on an on_shelf item."""
    # the following tests the circulation action CANCEL_REQUEST_1_1
    # on_shelf item with no pending requests, not possible to cancel a request.
    # a loan pid is a required parameter and not given
    with pytest.raises(TypeError):
        item_lib_martigny.cancel_item_request()
    assert item_lib_martigny.status == ItemStatus.ON_SHELF

    # the following tests the circulation action CANCEL_REQUEST_1_2
    # on_shelf item with pending requests, cancel a pending loan is possible.
    # the item remains on_shelf
    item, patron, loan = item_on_shelf_martigny_patron_and_loan_pending
    # add request for another patron
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    # cancel request
    params = {
        'pid': loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item.cancel_item_request(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    assert item.status == ItemStatus.ON_SHELF
    assert loan['state'] == LoanState.CANCELLED
Esempio n. 23
0
def test_checkin_on_item_in_transit_to_house_with_external_loans_transit(
        item6_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, loc_public_saxon, loc_public_saillon):
    """Test checkin on an in_transit item to house."""
    item, patron, loan = item6_in_transit_martigny_patron_and_loan_to_house

    # the following tests the circulation action CHECKIN_5_2_2_2
    # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests.
    # pickup location of first PENDING loan does not equal transaction library.
    # when the pickup library of the first pending request does not equal to
    # the to the item library, will cancel the current loan and then validate
    # the first pending request. item becomes in_transit and becomes
    # ITEM_IN_TRANSIT_FOR_PICKUP
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_saxon.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_saxon.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    params = {
        'transaction_location_pid': loc_public_saillon.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    item, actions = item.checkin(**params)

    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.CANCELLED
    assert requested_loan['state'] == LoanState.ITEM_IN_TRANSIT_FOR_PICKUP
Esempio n. 24
0
def test_checkin_on_item_in_transit_to_house_with_external_loans(
        item5_in_transit_martigny_patron_and_loan_to_house,
        loc_public_martigny, librarian_martigny_no_email,
        patron2_martigny_no_email, loc_public_saxon):
    """Test checkin on an in_transit item to house."""
    item, patron, loan = item5_in_transit_martigny_patron_and_loan_to_house

    # the following tests the circulation action CHECKIN_5_2_2_1
    # for an item in_transit (loan=IN_TRANSIT_TO_HOUSE) with pending requests.
    # pickup location of first PENDING loan does not equal transaction library.
    # when the pickup library of the first pending request does equal to
    # the to the item library, no action performed.
    # the item remains at_desk
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    assert requested_loan['state'] == LoanState.PENDING
    params = {
        'transaction_location_pid': loc_public_saxon.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }

    with pytest.raises(NoCirculationAction):
        item, actions = item.checkin(**params)
    item = Item.get_record_by_pid(item.pid)
    loan = Loan.get_record_by_pid(loan.pid)
    requested_loan = Loan.get_record_by_pid(requested_loan.pid)
    assert item.status == ItemStatus.IN_TRANSIT
    assert loan['state'] == LoanState.ITEM_IN_TRANSIT_TO_HOUSE
    assert requested_loan['state'] == LoanState.PENDING
Esempio n. 25
0
def test_loan_can_extend(client, patron_martigny, item_lib_martigny,
                         loc_public_martigny, librarian_martigny,
                         circulation_policies, json_header):
    """Test is loan can extend."""
    login_user(client, patron_martigny)
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid,
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny, loan_state=LoanState.ITEM_ON_LOAN,
        params=params, copy_item=True)

    list_url = url_for(
        'api_loan.can_extend', loan_pid=loan.pid)
    response = client.get(list_url, headers=json_header)
    assert response.status_code == 200
    assert get_json(response) == {
        'can': False,
        'reasons': ['Circulation policies disallows the operation.']
    }
Esempio n. 26
0
def item5_on_loan_martigny_patron_and_loan_on_loan(app, librarian_martigny,
                                                   item_lib_martigny,
                                                   loc_public_martigny,
                                                   patron_martigny,
                                                   circulation_policies):
    """Creates an item on_loan.

    :return item: the created or copied item.
    :return patron: the patron checked-out the item for.
    :return loan: the ITEM_ON_LOAN loan.
    """
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny,
        loan_state=LoanState.ITEM_ON_LOAN,
        params=params,
        copy_item=True)
    return item, patron_martigny, loan
Esempio n. 27
0
def item5_at_desk_martigny_patron_and_loan_at_desk(app, librarian_martigny,
                                                   item_lib_martigny,
                                                   loc_public_martigny,
                                                   patron_martigny,
                                                   circulation_policies):
    """Creates an item with a validated pending request.

    :return item: the created or copied item.
    :return patron: the patron placed the request.
    :return loan: the validated pending loan.
    """
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny,
        loan_state=LoanState.ITEM_AT_DESK,
        params=params,
        copy_item=True)
    return item, patron_martigny, loan
Esempio n. 28
0
def test_extend_on_item_on_loan_with_requests(
        item_on_loan_martigny_patron_and_loan_on_loan, loc_public_martigny,
        librarian_martigny_no_email, circulation_policies,
        patron2_martigny_no_email):
    """Test extend an on_loan item with requests."""
    # the following tests the circulation action EXTEND_3_2
    # for an on_loan item with requests, the extend action is not possible.
    item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan

    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, requested_loan = item_record_to_a_specific_loan_state(
        item=item,
        loan_state=LoanState.PENDING,
        params=params,
        copy_item=False)
    # test fails if no loan pid is given
    params = {
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    with pytest.raises(NoCirculationAction):
        item, actions = item.extend_loan(**params)
    assert item.status == ItemStatus.ON_LOAN
    # test fails if loan pid is given
    params = {
        'pid': loan.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid
    }
    with pytest.raises(NoCirculationAction):
        item, actions = item.extend_loan(**params)
    assert item.status == ItemStatus.ON_LOAN
Esempio n. 29
0
def test_reminder_notifications_after_extend(
    item_lib_martigny, patron_martigny, loc_public_martigny,
    librarian_martigny, circulation_policies, mailbox, client
):
    """Test any reminder notification could be resend after loan extension."""

    # STEP 1 - CREATE BASIC RESOURCES FOR THE TEST
    #   * Create a loan and update it to be considerate as "due soon".
    #   * Run the `notification-creation` task to create a DUE_SOON
    #     notification
    params = {
        'patron_pid': patron_martigny.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, loan = item_record_to_a_specific_loan_state(
        item=item_lib_martigny,
        loan_state=LoanState.ITEM_ON_LOAN,
        params=params, copy_item=True)

    # get the related cipo and check than an due_soon reminder exists
    cipo = get_circ_policy(loan)
    due_soon_reminder = cipo.get_reminder(DUE_SOON_REMINDER_TYPE)
    assert due_soon_reminder

    # Update the loan
    delay = due_soon_reminder.get('days_delay') - 1
    due_soon_date = datetime.now() - timedelta(days=delay)
    end_date = datetime.now() + timedelta(days=1)
    loan['due_soon_date'] = due_soon_date.astimezone(pytz.utc).isoformat()
    loan['end_date'] = end_date.astimezone(pytz.utc).isoformat()
    loan = loan.update(loan, dbcommit=True, reindex=True)
    assert loan.is_loan_due_soon()

    # run the create notification task and process notification.
    mailbox.clear()
    create_notifications(types=[NotificationType.DUE_SOON])
    process_notifications(NotificationType.DUE_SOON)

    first_notification = get_notification(loan, NotificationType.DUE_SOON)
    assert first_notification \
           and first_notification['status'] == NotificationStatus.DONE
    assert len(mailbox) == 1
    counter = NotificationsSearch()\
        .filter('term', context__loan__pid=loan.pid)\
        .filter('term', notification_type=NotificationType.DUE_SOON)\
        .count()
    assert counter == 1

    # STEP 2 - CHECK NOTIFICATIONS CREATION
    #   Run the `create_notification` task for DUE_SOON notification type.
    #   As a notification already exists, no new DUE_SOON#1 notifications
    #   should be created
    create_notifications(types=[NotificationType.DUE_SOON])
    query = NotificationsSearch() \
        .filter('term', context__loan__pid=loan.pid) \
        .filter('term', notification_type=NotificationType.DUE_SOON) \
        .source('pid').scan()
    notification_pids = [hit.pid for hit in query]
    assert len(notification_pids) == 1
    assert notification_pids[0] == first_notification.pid

    # STEP 3 - EXTEND THE LOAN
    #   * User has received the DUE_SOON message and extend the loan.
    #   * Get the new 'due_soon_date' it will be used later to create
    #     notifications
    login_user_via_session(client, librarian_martigny.user)
    params = dict(
        item_pid=item.pid,
        transaction_user_pid=librarian_martigny.pid,
        transaction_location_pid=loc_public_martigny.pid
    )
    res, _ = postdata(client, 'api_item.extend_loan', params)
    assert res.status_code == 200
    loan = Loan.get_record_by_pid(loan.pid)
    due_soon_date = ciso8601.parse_datetime(loan.get('due_soon_date'))

    # STEP 4 - CHECK NOTIFICATIONS CREATION
    #    Run again the `create_notification` task, again for DUE_SOON
    #    notification type. As the loan is extended, a new DUE_SOON
    #    notification should be created about this loan.
    #    Process the notification, check that this new notification isn't
    #    cancelled and well processed.
    process_date = due_soon_date + timedelta(days=1)
    create_notifications(
        types=[NotificationType.DUE_SOON],
        tstamp=process_date
    )
    counter = NotificationsSearch() \
        .filter('term', context__loan__pid=loan.pid) \
        .filter('term', notification_type=NotificationType.DUE_SOON) \
        .count()
    assert counter == 2
    process_notifications(NotificationType.DUE_SOON)
    assert len(mailbox) == 2
    second_notification = get_notification(loan, NotificationType.DUE_SOON)
    assert second_notification \
           and second_notification['status'] == NotificationStatus.DONE
    assert second_notification.pid != first_notification
Esempio n. 30
0
def test_loan_utils(client, patron_martigny_no_email,
                    patron2_martigny_no_email, circulation_policies,
                    item_lib_martigny, librarian_martigny_no_email,
                    loc_public_martigny):
    """Test loan utils methods."""
    loan_metadata = dict(item_lib_martigny)
    loan_metadata['item_pid'] = item_pid_to_object(item_lib_martigny.pid)
    if 'patron_pid' not in loan_metadata:
        loan_metadata['patron_pid'] = patron_martigny_no_email.pid
    # Create "virtual" Loan (not registered)
    loan = Loan(loan_metadata)
    # test that loan can successfully move to the pending state
    assert can_be_requested(loan)

    # test that loan without an item may not move to the pending state
    del loan['item_pid']
    with pytest.raises(Exception):
        assert can_be_requested(loan)

    # test a pending loan will be attached at the right organisation and
    # will not be considered as an active loan
    params = {
        'patron_pid': patron2_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    item, loan_pending_martigny = item_record_to_a_specific_loan_state(
        item=item_lib_martigny, loan_state=LoanState.PENDING,
        params=params, copy_item=True)

    assert loan_pending_martigny.patron_pid == patron2_martigny_no_email.pid
    assert not loan_pending_martigny.is_active
    assert loan_pending_martigny.organisation_pid

    # test required parameters for get_loans_by_patron_pid
    with pytest.raises(TypeError):
        assert get_loans_by_patron_pid()
    assert get_loans_by_patron_pid(patron2_martigny_no_email.pid)

    # test required parameters for get_last_transaction_loc_for_item
    with pytest.raises(TypeError):
        assert get_last_transaction_loc_for_item()

    # test the organisation of the loan is based on the item
    new_loan = deepcopy(loan_pending_martigny)
    assert new_loan.organisation_pid
    del new_loan['item_pid']
    with pytest.raises(IlsRecordError.PidDoesNotExist):
        new_loan.organisation_pid
    assert not can_be_requested(loan_pending_martigny)

    # test the allow request at the location level
    loc_public_martigny['allow_request'] = False
    loc_public_martigny.update(
        loc_public_martigny,
        dbcommit=True,
        reindex=True
    )
    flush_index(LocationsSearch.Meta.index)
    new_loan = {
        'patron_pid': patron_martigny_no_email.pid,
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny_no_email.pid,
        'pickup_location_pid': loc_public_martigny.pid
    }
    with pytest.raises(RecordCannotBeRequestedError):
        item, loan_pending_martigny = item_record_to_a_specific_loan_state(
            item=item_lib_martigny, loan_state=LoanState.PENDING,
            params=params, copy_item=True)

    loc_public_martigny['allow_request'] = True
    loc_public_martigny.update(
        loc_public_martigny,
        dbcommit=True,
        reindex=True
    )
    flush_index(LocationsSearch.Meta.index)
    item, loan_pending_martigny = item_record_to_a_specific_loan_state(
        item=item_lib_martigny, loan_state=LoanState.PENDING,
        params=params, copy_item=True)
    assert loan_pending_martigny['state'] == LoanState.PENDING