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'
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'
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
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()