Exemple #1
0
def test_patron_transaction_create(db, es_clear,
                                   patron_transaction_overdue_martigny,
                                   org_martigny):
    """Test patron transaction creation."""
    patron_transaction = deepcopy(patron_transaction_overdue_martigny)
    patron_transaction['status'] = 'no_status'
    import jsonschema
    with pytest.raises(jsonschema.exceptions.ValidationError):
        PatronTransaction.create(patron_transaction, delete_pid=True)
    db.session.rollback()

    next_pid = PatronTransaction.provider.identifier.next()
    patron_transaction['status'] = 'open'
    record = PatronTransaction.create(patron_transaction, delete_pid=True)
    next_pid += 1
    assert record == patron_transaction
    assert record.get('pid') == str(next_pid)

    pttr = PatronTransaction.get_record_by_pid(str(next_pid))
    assert pttr == patron_transaction

    fetched_pid = fetcher(pttr.id, pttr)
    assert fetched_pid.pid_value == str(next_pid)
    assert fetched_pid.pid_type == 'pttr'

    can, reasons = patron_transaction_overdue_martigny.can_delete
    assert not can
    assert reasons['links']['events']

    assert patron_transaction_overdue_martigny.currency == \
        org_martigny.get('default_currency')
def test_get_transactions_pids_for_patron(patron_sion_no_email):
    """Test function get_transactions_pids_for_patron."""
    assert PatronTransaction.get_transactions_count_for_patron(
        patron_sion_no_email.pid
    ) == 2
    assert len(list(PatronTransaction.get_transactions_pids_for_patron(
        patron_sion_no_email.pid, status='open'
    ))) == 2
    assert len(list(PatronTransaction.get_transactions_pids_for_patron(
        patron_sion_no_email.pid, status='closed'
    ))) == 0
def test_patron_transaction_es_mapping(es, db,
                                       patron_transaction_overdue_martigny):
    """Test patron_transaction elasticsearch mapping."""
    search = PatronTransactionsSearch()
    mapping = get_mapping(search.Meta.index)
    assert mapping
    PatronTransaction.create(patron_transaction_overdue_martigny,
                             dbcommit=True,
                             reindex=True,
                             delete_pid=True)
    assert mapping == get_mapping(search.Meta.index)
def test_patron_payment(client, librarian_martigny,
                        patron_transaction_overdue_event_martigny):
    """Test patron payment."""
    ptre = patron_transaction_overdue_event_martigny
    transaction = ptre.patron_transaction
    calculated_amount = sum(event.amount for event in transaction.events)
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert calculated_amount == transaction.total_amount == 2.00

    login_user_via_session(client, librarian_martigny.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    payment = deepcopy(ptre)

    # STEP#1 :: PARTIAL PAYMENT WITH TOO MUCH DECIMAL
    #   Try to pay a part of the transaction amount, but according to
    #   event amount restriction, only 2 decimals are allowed.
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = 0.545
    payment['operator'] = {
        '$ref': get_ref_for_pid('patrons', librarian_martigny.pid)
    }
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 400

    # STEP#2 :: PARTIAL PAYMENT WITH GOOD NUMBER OF DECIMALS
    #   Despite if a set a number with 3 decimals, if this number represent
    #   the value of a 2 decimals, it's allowed
    payment['amount'] = 0.540
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 1.46
    assert transaction.status == 'open'

    # STEP#3 :: PAY TOO MUCH MONEY
    #   Try to proceed a payment with too much money, the system must
    #   reject the payment
    payment['amount'] = 2
    res, data = postdata(client, post_entrypoint, payment)
    assert res.status_code == 400

    # STEP#4 :: PAY THE REST
    #   Conclude the transaction by creation of a payment for the rest of the
    #   transaction
    payment['amount'] = transaction.total_amount
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 0
    assert transaction.status == 'closed'
def test_patron_payment(
        client, librarian_martigny_no_email,
        librarian_sion_no_email, patron_transaction_overdue_event_martigny):
    """Test patron payment."""
    transaction = \
        patron_transaction_overdue_event_martigny.patron_transaction()
    calculated_amount = sum([event.amount
                             for event in
                             transaction.events])
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert calculated_amount == transaction.total_amount == 2.00

    login_user_via_session(client, librarian_martigny_no_email.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    payment = deepcopy(patron_transaction_overdue_event_martigny)

    # partial payment
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = 1.00
    payment['operator'] = {'$ref': get_ref_for_pid(
        'patrons', librarian_martigny_no_email.pid)}
    res, _ = postdata(
        client,
        post_entrypoint,
        payment
    )
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == calculated_amount - 1.00
    assert transaction.status == 'open'

    # full payment
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = transaction.total_amount
    res, _ = postdata(
        client,
        post_entrypoint,
        payment
    )
    assert res.status_code == 201

    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.total_amount == 0.00
    assert transaction.status == 'closed'
Exemple #6
0
def test_anonymizer_job(item_on_loan_martigny_patron_and_loan_on_loan,
                        librarian_martigny, loc_public_martigny):
    """Test loan anonymizer job."""
    msg = loan_anonymizer(dbcommit=True, reindex=True)

    item, patron, loan = item_on_loan_martigny_patron_and_loan_on_loan
    # make the loan overdue
    end_date = datetime.now(timezone.utc) - timedelta(days=10)
    loan['end_date'] = end_date.isoformat()
    loan.update(loan, dbcommit=True, reindex=True)

    create_notifications(types=[
        Notification.DUE_SOON_NOTIFICATION_TYPE,
        Notification.OVERDUE_NOTIFICATION_TYPE
    ])
    flush_index(NotificationsSearch.Meta.index)
    flush_index(LoansSearch.Meta.index)

    assert not loan.concluded(loan)
    assert not loan.can_anonymize(loan_data=loan)

    patron.user.profile.keep_history = True

    params = {
        'transaction_location_pid': loc_public_martigny.pid,
        'transaction_user_pid': librarian_martigny.pid
    }
    item.checkin(**params)
    loan = Loan.get_record_by_pid(loan.pid)
    # item checked-in and has no open events
    assert not loan.concluded(loan)
    assert not loan.can_anonymize(loan_data=loan)

    msg = loan_anonymizer(dbcommit=True, reindex=True)
    assert msg == 'number_of_loans_anonymized: 0'

    patron.user.profile.keep_history = False
    # close open transactions and notifications
    for transaction in PatronTransaction.get_transactions_by_patron_pid(
            patron.get('pid'), 'open'):
        transaction = PatronTransaction.get_record_by_pid(transaction.pid)
        transaction['status'] = 'closed'
        transaction.update(transaction, dbcommit=True, reindex=True)
    msg = loan_anonymizer(dbcommit=True, reindex=True)
    assert msg == 'number_of_loans_anonymized: 2'
def test_patron_transaction_create(db, es_clear,
                                   patron_transaction_overdue_martigny):
    """Test patron transaction creation."""
    patron_transaction = deepcopy(patron_transaction_overdue_martigny)
    patron_transaction['status'] = 'no_status'
    import jsonschema
    with pytest.raises(jsonschema.exceptions.ValidationError):
        record = PatronTransaction.create(patron_transaction, delete_pid=True)

    db.session.rollback()

    patron_transaction['status'] = 'open'
    record = PatronTransaction.create(patron_transaction, delete_pid=True)
    assert record == patron_transaction
    assert record.get('pid') == '2'

    pttr = PatronTransaction.get_record_by_pid('2')
    assert pttr == patron_transaction

    fetched_pid = fetcher(pttr.id, pttr)
    assert fetched_pid.pid_value == '2'
    assert fetched_pid.pid_type == 'pttr'
Exemple #8
0
def test_patron_pending_subscription(client, patron_type_grown_sion,
                                     patron_sion_no_email,
                                     librarian_sion_no_email,
                                     patron_transaction_overdue_event_martigny,
                                     lib_sion):
    """Test get pending subscription for patron."""
    # At the beginning, `patron_sion_no_email` should have one pending
    # subscription.
    pending_subscription = patron_sion_no_email.get_pending_subscriptions()
    assert len(pending_subscription) == 1

    # Pay this subscription.
    login_user_via_session(client, librarian_sion_no_email.user)
    post_entrypoint = 'invenio_records_rest.ptre_list'
    trans_pid = extracted_data_from_ref(
        pending_subscription[0]['patron_transaction'], data='pid')
    transaction = PatronTransaction.get_record_by_pid(trans_pid)
    payment = deepcopy(patron_transaction_overdue_event_martigny)
    del payment['pid']
    payment['type'] = 'payment'
    payment['subtype'] = 'cash'
    payment['amount'] = transaction.total_amount
    payment['operator'] = {
        '$ref': get_ref_for_pid('patrons', librarian_sion_no_email.pid)
    }
    payment['library'] = {'$ref': get_ref_for_pid('libraries', lib_sion.pid)}
    payment['parent'] = pending_subscription[0]['patron_transaction']
    res, _ = postdata(client, post_entrypoint, payment)
    assert res.status_code == 201
    transaction = PatronTransaction.get_record_by_pid(transaction.pid)
    assert transaction.status == 'closed'

    # reload the patron and check the pending subscription. As we paid the
    # previous subscription, there will be none pending subscription
    patron_sion_no_email = Patron.get_record_by_pid(patron_sion_no_email.pid)
    pending_subscription = patron_sion_no_email.get_pending_subscriptions()
    assert len(pending_subscription) == 0
Exemple #9
0
def test_patron_subscription_transaction(patron_type_youngsters_sion,
                                         patron_sion_no_email):
    """Test the creation of a subscription transaction for a patron."""
    subscription_start_date = datetime.now()
    subscription_end_date = add_years(subscription_start_date, 1)
    assert subscription_end_date.year == subscription_start_date.year + 1
    assert subscription_end_date.month == subscription_start_date.month
    assert subscription_end_date.day == subscription_start_date.day

    subscription = PatronTransaction.create_subscription_for_patron(
        patron_sion_no_email,
        patron_type_youngsters_sion,
        subscription_start_date,
        subscription_end_date,
        dbcommit=True,
        reindex=True,
        delete_pid=True)
    assert subscription.get_number_of_patron_transaction_events() == 1
    event = list(subscription.events)[0]
    assert event.get('type') == 'fee'
    assert event.get('subtype') == 'other'
    assert event.get('amount') == subscription.get('total_amount')
def test_holding_requests(client, patron_martigny, loc_public_martigny,
                          circulation_policies, librarian_martigny,
                          holding_lib_martigny_w_patterns, lib_martigny,
                          item_lib_martigny, org_martigny):
    """Test holding patron request."""
    login_user_via_session(client, patron_martigny.user)
    holding = holding_lib_martigny_w_patterns
    description = 'Year: 2000 / volume: 15 / number: 22 / pages: 11-12'
    # test fails when there is a missing description or holding_pid
    res, data = postdata(
        client, 'api_holding.patron_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid))
    assert res.status_code == 400
    res, data = postdata(
        client, 'api_holding.patron_request',
        dict(description=description,
             pickup_location_pid=loc_public_martigny.pid))
    assert res.status_code == 404
    # test passes when all required parameters are given
    res, data = postdata(
        client, 'api_holding.patron_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid,
             description=description))
    assert res.status_code == 200
    loan = Loan.get_record_by_pid(
        data.get('action_applied')[LoanAction.REQUEST].get('pid'))
    assert loan.state == LoanState.PENDING
    item = Item.get_record_by_pid(loan.item_pid)
    assert item.get('type') == TypeOfItem.PROVISIONAL
    assert item.status == ItemStatus.ON_SHELF
    assert item.holding_pid == holding.pid
    assert item.get('enumerationAndChronology') == description
    # checkout the item to the requested patron
    login_user_via_session(client, librarian_martigny.user)
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item.pid,
            patron_pid=patron_martigny.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')
    assert loan_pid == loan.pid
    item = Item.get_record_by_pid(item.pid)
    assert item.status == ItemStatus.ON_LOAN
    # return the item at the owning library
    res, data = postdata(
        client, 'api_item.checkin',
        dict(item_pid=item.pid,
             transaction_location_pid=loc_public_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 200
    item = Item.get_record_by_pid(item.pid)
    assert item.status == ItemStatus.ON_SHELF

    # test requests made by a librarian
    # test fails when there are missing parameters
    res, data = postdata(
        client, 'api_holding.librarian_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid,
             description=description,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 400
    res, data = postdata(
        client, 'api_holding.librarian_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid,
             description=description,
             patron_pid=patron_martigny.pid,
             transaction_library_pid=lib_martigny.pid))
    assert res.status_code == 400
    res, data = postdata(
        client, 'api_holding.librarian_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid,
             patron_pid=patron_martigny.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 400
    # test passes when all required parameters are given
    res, data = postdata(
        client, 'api_holding.librarian_request',
        dict(holding_pid=holding.pid,
             pickup_location_pid=loc_public_martigny.pid,
             description=description,
             patron_pid=patron_martigny.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny.pid))
    assert res.status_code == 200
    loan_2 = Loan.get_record_by_pid(
        data.get('action_applied')[LoanAction.REQUEST].get('pid'))
    assert loan_2.state == LoanState.PENDING
    item_2 = Item.get_record_by_pid(loan_2.item_pid)
    assert item_2.get('type') == TypeOfItem.PROVISIONAL
    assert item_2.status == ItemStatus.ON_SHELF
    assert item_2.holding_pid == holding.pid
    assert item_2.get('enumerationAndChronology') == description
    assert item_2.pid != item.pid

    all_item_pids = [pid for pid in Item.get_all_pids()]
    assert all_item_pids

    # test delete provisional items with no active fees/loans
    report = delete_provisional_items()
    assert report.get('numner_of_deleted_items')
    assert report.get('number_of_candidate_items_to_delete')
    # assert that not deleted items are either having loans/fees or not
    # provisional items
    left_item_pids = [pid for pid in Item.get_all_pids()]
    assert left_item_pids
    for pid in left_item_pids:
        record = Item.get_record_by_pid(pid)
        can, _ = record.can_delete
        assert not can or record.get('type') != TypeOfItem.PROVISIONAL
    # item_2 has pending loans then it should not be removed
    assert item_2.pid in left_item_pids
    assert item_2.pid in get_provisional_items_pids_candidate_to_delete()
    # add fee to item_2 and make sure it will not be candidate at the deletion.
    data = {
        'loan': {
            '$ref': get_ref_for_pid('loanid', loan_2.pid)
        },
        'patron': {
            '$ref': get_ref_for_pid('patrons', patron_martigny.pid)
        },
        'organisation': {
            '$ref': get_ref_for_pid('org', org_martigny.pid)
        },
        'status': 'open',
        'total_amount': 0.6,
        'type': 'overdue',
        'creation_date': datetime.now(timezone.utc).isoformat()
    }
    PatronTransaction.create(data, dbcommit=True, reindex=True)
    assert item_2.pid not in get_provisional_items_pids_candidate_to_delete()