Exemple #1
0
def test_notification_create(es_clear, dummy_notification,
                             loan_validated_martigny, mailbox):
    """Test notification creation."""
    notif = deepcopy(dummy_notification)
    notif_data = {
        'loan_url': 'https://ils.rero.ch/api/loans/',
        'pid': loan_validated_martigny.get('pid')
    }
    loan_ref = '{loan_url}{pid}'.format(**notif_data)
    notif['loan'] = {"$ref": loan_ref}

    notification = Notification.create(notif,
                                       dbcommit=True,
                                       delete_pid=True,
                                       reindex=True)
    assert notification == notif

    pid = notification.get('pid')

    notification = Notification.get_record_by_pid(pid)
    assert notification == notif

    fetched_pid = fetcher(notification.id, notification)
    assert fetched_pid.pid_value == pid
    assert fetched_pid.pid_type == 'notif'

    notification.dispatch(enqueue=False, verbose=True)
    assert len(mailbox) == 1
Exemple #2
0
def test_recall_notification_without_email(
        client, patron_sion_without_email, lib_martigny, json_header,
        patron2_martigny_no_email, item3_lib_martigny,
        librarian_martigny_no_email, circulation_policies, loc_public_martigny,
        mailbox):
    """Test recall notification."""
    # process all notifications still in the queue
    Notification.process_notifications()
    mailbox.clear()
    login_user_via_session(client, librarian_martigny_no_email.user)
    res, data = postdata(
        client, 'api_item.checkout',
        dict(
            item_pid=item3_lib_martigny.pid,
            patron_pid=patron_sion_without_email.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny_no_email.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_notified(notification_type='recall')
    # test notification
    res, data = postdata(
        client, 'api_item.librarian_request',
        dict(item_pid=item3_lib_martigny.pid,
             pickup_location_pid=loc_public_martigny.pid,
             patron_pid=patron2_martigny_no_email.pid,
             transaction_library_pid=lib_martigny.pid,
             transaction_user_pid=librarian_martigny_no_email.pid))
    assert res.status_code == 200

    request_loan_pid = data.get('action_applied')[LoanAction.REQUEST].get(
        'pid')

    flush_index(NotificationsSearch.Meta.index)

    assert loan.is_notified(notification_type='recall')

    notification = get_recall_notification(loan)
    assert notification.loan_pid == loan.pid

    assert not loan.is_notified(notification_type='availability')
    assert not get_availability_notification(loan)
    assert Notification.process_notifications() == {
        'send': 1,
        'reject': 0,
        'error': 0
    }

    # no email as the patron does not have an email
    assert len(mailbox) == 0
    mailbox.clear()
def test_loan(app, notification_schema, dummy_notification):
    """Test loan for notification jsonschemas."""
    validate(dummy_notification, notification_schema)

    with pytest.raises(ValidationError):
        dummy_notification['context']['loan'] = 25
        validate(dummy_notification, notification_schema)

    with pytest.raises(ValidationError):
        notif = Notification(dummy_notification)
        del notif['context']['loan']
        notif.validate()
Exemple #4
0
def test_notification_es_mapping(dummy_notification, loan_validated_martigny):
    """Test notification elasticsearch mapping."""

    search = NotificationsSearch()
    mapping = get_mapping(search.Meta.index)
    assert mapping

    notif = deepcopy(dummy_notification)
    validated_pid = loan_validated_martigny.get('pid')
    loan_ref = f'https://bib.rero.ch/api/loans/{validated_pid}'
    notif['context']['loan']['$ref'] = loan_ref
    Notification.create(notif, dbcommit=True, delete_pid=True, reindex=True)
    assert mapping == get_mapping(search.Meta.index)
Exemple #5
0
def test_notification_es_mapping(dummy_notification, loan_validated_martigny):
    """Test notification elasticsearch mapping."""

    search = NotificationsSearch()
    mapping = get_mapping(search.Meta.index)
    assert mapping

    notif = deepcopy(dummy_notification)
    notif_data = {
        'loan_url': 'https://ils.rero.ch/api/loans/',
        'pid': loan_validated_martigny.get('pid')
    }
    loan_ref = '{loan_url}{pid}'.format(**notif_data)
    notif['loan'] = {"$ref": loan_ref}

    Notification.create(notif, dbcommit=True, delete_pid=True, reindex=True)

    assert mapping == get_mapping(search.Meta.index)
def test_notifications_post_put_delete(
        client, dummy_notification, loan_validated_martigny, json_header):
    """Test record delete and update."""

    record = deepcopy(dummy_notification)
    del record['pid']
    notif_data = {
        'loan_url': 'https://ils.rero.ch/api/loans/',
        'pid': loan_validated_martigny.get('pid')
    }
    loan_ref = '{loan_url}{pid}'.format(**notif_data)
    record['loan'] = {"$ref": loan_ref}
    notif = Notification.create(
        record,
        dbcommit=True,
        reindex=True,
        delete_pid=True
    )
    assert notif == record
    flush_index(NotificationsSearch.Meta.index)
    pid = notif.get('pid')

    item_url = url_for('invenio_records_rest.notif_item', pid_value=pid)
    list_url = url_for('invenio_records_rest.notif_list', q='pid:pid')

    new_record = deepcopy(record)

    # Create record / POST
    new_record['pid'] = 'x'
    res, data = postdata(
        client,
        'invenio_records_rest.notif_list',
        new_record
    )
    assert res.status_code == 201

    flush_index(NotificationsSearch.Meta.index)

    # Check that the returned record matches the given data
    assert data['metadata'] == new_record

    res = client.get(item_url)
    assert res.status_code == 200
    data = get_json(res)
    assert notif == data['metadata']

    # Update record/PUT
    data['notification_type'] = 'due_soon'
    res = client.put(
        item_url,
        data=json.dumps(data),
        headers=json_header
    )
    assert res.status_code == 200
    # assert res.headers['ETag'] != '"{}"'.format(librarie.revision_id)

    # Check that the returned record matches the given data
    data = get_json(res)
    assert data['metadata']['notification_type'] == 'due_soon'

    res = client.get(item_url)
    assert res.status_code == 200

    data = get_json(res)
    assert data['metadata']['notification_type'] == 'due_soon'

    res = client.get(list_url)
    assert res.status_code == 200

    # Delete record/DELETE
    res = client.delete(item_url)
    assert res.status_code == 204

    res = client.get(item_url)
    assert res.status_code == 410

    links = notif.get_links_to_me()
    assert links == {}

    assert notif.can_delete

    reasons = notif.reasons_not_to_delete()
    assert reasons == {}

    notif.delete(dbcommit=True, delindex=True)
Exemple #7
0
def test_cancel_notifications(
    client, patron_martigny, lib_martigny, item_lib_martigny,
    librarian_martigny, loc_public_martigny, circulation_policies, mailbox
):
    """Test cancel notifications."""
    login_user_via_session(client, librarian_martigny.user)
    # CREATE and VALIDATE a request ...
    res, data = postdata(
        client,
        'api_item.librarian_request',
        dict(
            item_pid=item_lib_martigny.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 == 200
    request_loan_pid = data.get(
        'action_applied')[LoanAction.REQUEST].get('pid')

    flush_index(NotificationsSearch.Meta.index)
    res, data = postdata(
        client,
        'api_item.validate_request',
        dict(
            pid=request_loan_pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid
        )
    )
    assert res.status_code == 200
    # At this time, an AVAILABILITY notification should be create but not yet
    # dispatched
    loan = Loan.get_record_by_pid(request_loan_pid)
    notification = get_notification(loan, NotificationType.AVAILABILITY)
    assert notification \
           and notification['status'] == NotificationStatus.CREATED

    # BORROW the requested item
    res, data = postdata(
        client,
        'api_item.checkout',
        dict(
            item_pid=item_lib_martigny.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')
    loan = Loan.get_record_by_pid(loan_pid)

    # Try to dispatch pending availability notifications.
    # As the item is now checkout, then the availability notification is not
    # yet relevant.
    mailbox.clear()
    process_notifications(NotificationType.AVAILABILITY)

    notification = get_notification(loan, NotificationType.AVAILABILITY)
    assert notification and \
           notification['status'] == NotificationStatus.CANCELLED
    assert len(mailbox) == 0

    # restore to initial state
    res, data = postdata(
        client,
        'api_item.checkin',
        dict(
            item_pid=item_lib_martigny.pid,
            # patron_pid=patron_martigny.pid,
            transaction_location_pid=loc_public_martigny.pid,
            transaction_user_pid=librarian_martigny.pid,
        )
    )
    assert res.status_code == 200
    mailbox.clear()

    # Test REMINDERS notifications.
    #   reminders notification check about the end_date. As the loan end_date
    #   is not yet over, the notification could be cancelled.

    notification = loan.create_notification(_type=NotificationType.DUE_SOON)[0]
    can_cancel, _ = notification.can_be_cancelled()
    assert not can_cancel
    process_notifications(NotificationType.DUE_SOON)
    notification = Notification.get_record_by_pid(notification.pid)
    assert notification['status'] == NotificationStatus.DONE
    flush_index(NotificationsSearch.Meta.index)

    # try to create a new DUE_SOON notification for the same loan
    record = {
        'creation_date': datetime.now(timezone.utc).isoformat(),
        'notification_type': NotificationType.DUE_SOON,
        'context': {
            'loan': {'$ref': get_ref_for_pid('loans', loan.pid)},
            'reminder_counter': 0
        }
    }
    notification = Notification.create(record)
    can_cancel, _ = notification.can_be_cancelled()
    assert can_cancel
    Dispatcher.dispatch_notifications([notification.pid])
    notification = Notification.get_record_by_pid(notification.pid)
    assert notification['status'] == NotificationStatus.CANCELLED
Exemple #8
0
def test_notifications_post_put_delete(
        client, dummy_notification, loan_validated_martigny, json_header):
    """Test record delete and update."""

    record = deepcopy(dummy_notification)
    del record['pid']
    loan_ref = get_ref_for_pid('loans', loan_validated_martigny.get('pid'))
    record['context']['loan'] = {'$ref': loan_ref}
    notif = Notification.create(
        record,
        dbcommit=True,
        reindex=True,
        delete_pid=True
    )
    assert notif == record
    flush_index(NotificationsSearch.Meta.index)
    pid = notif.get('pid')

    item_url = url_for('invenio_records_rest.notif_item', pid_value=pid)
    list_url = url_for('invenio_records_rest.notif_list', q='pid:pid')

    new_record = deepcopy(record)

    # Create record / POST
    new_record['pid'] = 'x'
    res, data = postdata(
        client,
        'invenio_records_rest.notif_list',
        new_record
    )
    assert res.status_code == 201

    flush_index(NotificationsSearch.Meta.index)

    # Check that the returned record matches the given data
    assert data['metadata'] == new_record

    res = client.get(item_url)
    assert res.status_code == 200
    data = get_json(res)
    assert notif == data['metadata']

    # Update record/PUT
    data = data['metadata']
    data['notification_type'] = NotificationType.DUE_SOON
    res = client.put(
        item_url,
        data=json.dumps(data),
        headers=json_header
    )
    assert res.status_code == 200

    # Check that the returned record matches the given data
    data = get_json(res)
    assert data['metadata']['notification_type'] == NotificationType.DUE_SOON

    res = client.get(item_url)
    assert res.status_code == 200

    data = get_json(res)
    assert data['metadata']['notification_type'] == NotificationType.DUE_SOON

    res = client.get(list_url)
    assert res.status_code == 200

    # Delete record/DELETE
    res = client.delete(item_url)
    assert res.status_code == 204

    res = client.get(item_url)
    assert res.status_code == 410

    can, reasons = notif.can_delete
    assert can
    assert reasons == {}

    notif.delete(dbcommit=True, delindex=True)
def test_send_order(app, client, librarian_martigny, lib_martigny,
                    acq_order_fiction_martigny,
                    acq_order_line_fiction_martigny, vendor_martigny,
                    acq_order_line2_fiction_martigny,
                    acq_order_line3_fiction_martigny, mailbox):
    """Test send order notification API."""
    login_user_via_session(client, librarian_martigny.user)
    acor = acq_order_fiction_martigny
    address = vendor_martigny.get('default_contact').get('email')
    emails = [{'type': 'cc', 'address': address}]
    mailbox.clear()
    # test when parent order is not in database
    res, data = postdata(client,
                         'api_order.send_order',
                         data=dict(emails=emails),
                         url_data=dict(order_pid='toto'))
    assert res.status_code == 404
    # test when email data is not provided
    res, data = postdata(client,
                         'api_order.send_order',
                         url_data=dict(order_pid=acor.pid))
    assert res.status_code == 400
    # test when email data provided but empty
    res, data = postdata(client,
                         'api_order.send_order',
                         data=dict(emails=[]),
                         url_data=dict(order_pid=acor.pid))
    assert res.status_code == 400
    # test when email data provided and has no "to" email address
    res, data = postdata(client,
                         'api_order.send_order',
                         data=dict(emails=emails),
                         url_data=dict(order_pid=acor.pid))
    assert res.status_code == 400
    assert 'required' in data['message'] and '`to`' in data['message']
    # have an order line with a status different than approved and ensure it
    # will not be ordered
    l3 = AcqOrderLine.get_record_by_pid(acq_order_line3_fiction_martigny.pid)
    l3['is_cancelled'] = True
    l3.update(l3, dbcommit=True, reindex=True)

    # test send order with correct input parameters
    emails = [{
        'type': 'to',
        'address': address
    }, {
        'type': 'reply_to',
        'address': lib_martigny.get('email')
    }]
    res, data = postdata(client,
                         'api_order.send_order',
                         data=dict(emails=emails),
                         url_data=dict(order_pid=acor.pid))
    data = get_json(res)
    assert res.status_code == 200

    # Reload the order and related order lines to check updated fields
    #   * the order status should be ORDERED
    #   * each order lines should be ORDERED (except for l3 = CANCELLED)
    acor = AcqOrder.get_record_by_pid(acor.pid)
    l1 = AcqOrderLine.get_record_by_pid(acq_order_line_fiction_martigny.pid)
    l2 = AcqOrderLine.get_record_by_pid(acq_order_line2_fiction_martigny.pid)
    l3 = AcqOrderLine.get_record_by_pid(acq_order_line3_fiction_martigny.pid)
    assert l1.status == AcqOrderLineStatus.ORDERED
    assert l1.order_date
    assert l2.status == AcqOrderLineStatus.ORDERED
    assert l2.order_date
    assert l3.status == AcqOrderLineStatus.CANCELLED
    assert not l3.order_date
    assert acor.status == AcqOrderStatus.ORDERED

    # ensure that created notification is well constructed from the associated
    # order and vendor
    notification_pid = data.get('data').get('pid')
    notif = Notification.get_record_by_pid(notification_pid)
    assert notif.organisation_pid == acor.organisation_pid
    assert notif.aggregation_key == str(notif.id)
    assert notif.type == NotificationType.ACQUISITION_ORDER
    assert notif.status == NotificationStatus.DONE
    assert notif.acq_order_pid == acor.pid
    assert notif.library_pid == acor.library_pid
    assert notif.can_be_cancelled() == (False, None)
    assert notif.get_communication_channel() == NotificationChannel.EMAIL
    assert notif.get_language_to_use() == \
        vendor_martigny.get('communication_language')
    assert address in notif.get_recipients(RecipientType.TO)

    # Check mail content
    message = mailbox[-1]
    shipping = lib_martigny['acquisition_settings']['shipping_informations']
    assert shipping.get('extra') and shipping.get('extra') in message.body
def test_acquisition_reception_workflow(
    client, rero_json_header, org_martigny,
    lib_martigny, lib_saxon, budget_2020_martigny,
    vendor_martigny, librarian_martigny, document
):
    """Test complete acquisition workflow."""

    def assert_account_data(accounts):
        """assert account informations."""
        for acc, (balance, expenditure, encumbrance) in accounts.items():
            assert acc.expenditure_amount == expenditure
            assert acc.encumbrance_amount == encumbrance
            assert acc.remaining_balance == balance

    # STEP 1 :: Create account structures
    #   * Create accounts for Martigny library.
    #     - MTY.0000.00' (root)
    #       --> MTY.000b.00
    #       --> MTY.000s.00
    #   * Create accounts for Saxon library.
    #     - SXN.0000.00 (root)
    #       --> SXN.000b.00
    #       --> SXN.000s.00
    data = {
        'name': 'Martigny root account',
        'number': 'MTY.0000.00',
        'allocated_amount': 10000,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)}
    }
    m_root_acc = _make_resource(client, 'acac', data)
    data = {
        'name': 'Martigny Books child account',
        'number': 'MTY.000b.00',
        'allocated_amount': 2000,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)},
        'parent': {'$ref': get_ref_for_pid('acac', m_root_acc.pid)},
    }
    m_books_acc = _make_resource(client, 'acac', data)
    data = {
        'name': 'Martigny Serials child account',
        'number': 'MTY.000s.00',
        'allocated_amount': 3000,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)},
        'parent': {'$ref': get_ref_for_pid('acac', m_root_acc.pid)},
    }
    m_serials_acc = _make_resource(client, 'acac', data)
    data = {
        'name': 'Saxon root account',
        'number': 'SXN.0000.00',
        'allocated_amount': 20000,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_saxon.pid)}
    }
    s_root_acc = _make_resource(client, 'acac', data)
    data = {
        'name': 'Saxon Books chid account',
        'number': 'SXN.000b.00',
        'allocated_amount': 2500,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_saxon.pid)},
        'parent': {'$ref': get_ref_for_pid('acac', s_root_acc.pid)},
    }
    s_books_acc = _make_resource(client, 'acac', data)
    data = {
        'name': 'Saxon Serials chid account',
        'number': 'SXN.000s.00',
        'allocated_amount': 4000,
        'budget': {'$ref': get_ref_for_pid('budg', budget_2020_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_saxon.pid)},
        'parent': {'$ref': get_ref_for_pid('acac', s_root_acc.pid)},
    }
    s_serials_acc = _make_resource(client, 'acac', data)

    # For each account check data. the dict values are tuples. Each tuple
    # define `balance`, `expenditure`, `encumbrance`
    manual_controls = {
        m_root_acc: ((5000, 10000), (0, 0), (0, 0)),
        m_books_acc: ((2000, 2000), (0, 0), (0, 0)),
        m_serials_acc: ((3000, 3000), (0, 0), (0, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    # STEP 2 :: CREATE REAL ORDER
    #   * Create an order for Martigny library
    #   * Adds 6 order lines for this order
    #       martigny_books_account:
    #           line_1: quantity: 5 of amount 10   = 50
    #           line_2: quantity: 2 of amount 50   = 100
    #           line_3: quantity: 3 of amount 100  = 300
    #                                        Total = 450
    #       martigny_serials_account:
    #           line_1: quantity: 3 of amount 15   = 45
    #           line_2: quantity: 1 of amount 150  = 150
    #           line_3: quantity: 10 of amount 7   = 70
    #                                        Total = 265
    #       Items quantity = 24: order total amount = 715
    login_user_via_session(client, librarian_martigny.user)
    data = {
        'vendor': {'$ref': get_ref_for_pid('vndr', vendor_martigny.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)},
        'type': 'monograph',
    }
    order = _make_resource(client, 'acor', data)
    assert order['reference'] == f'ORDER-{order.pid}'
    assert order.get_order_provisional_total_amount() == 0
    assert order.status == AcqOrderStatus.PENDING
    assert order.can_delete

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_books_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 5,
        'amount': 10
    }
    order_line_1 = _make_resource(client, 'acol', data)
    order_line_1_ref = get_ref_for_pid('acol', order_line_1.pid)
    assert order_line_1.get('total_amount') == 50
    assert order_line_1.quantity == 5
    assert order_line_1.received_quantity == 0
    assert order_line_1.unreceived_quantity == 5
    assert not order_line_1.is_cancelled
    assert order_line_1.status == AcqOrderLineStatus.APPROVED

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_books_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 2,
        'amount': 50
    }
    order_line_2 = _make_resource(client, 'acol', data)
    order_line_2_ref = get_ref_for_pid('acol', order_line_2.pid)
    assert order_line_2.get('total_amount') == 100
    assert order_line_2.quantity == 2
    assert order_line_2.received_quantity == 0
    assert order_line_2.unreceived_quantity == 2
    assert not order_line_2.is_cancelled
    assert order_line_2.status == AcqOrderLineStatus.APPROVED

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_books_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 3,
        'amount': 100
    }
    order_line_3 = _make_resource(client, 'acol', data)
    order_line_3_ref = get_ref_for_pid('acol', order_line_3.pid)
    assert order_line_3.get('total_amount') == 300
    assert order_line_3.quantity == 3
    assert order_line_3.received_quantity == 0
    assert order_line_3.unreceived_quantity == 3
    assert not order_line_3.is_cancelled
    assert order_line_3.status == AcqOrderLineStatus.APPROVED

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_serials_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 3,
        'amount': 15
    }
    order_line_4 = _make_resource(client, 'acol', data)
    order_line_4_ref = get_ref_for_pid('acol', order_line_4.pid)
    assert order_line_4.get('total_amount') == 45
    assert order_line_4.quantity == 3
    assert order_line_4.received_quantity == 0
    assert order_line_4.unreceived_quantity == 3
    assert not order_line_4.is_cancelled
    assert order_line_4.status == AcqOrderLineStatus.APPROVED

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_serials_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 1,
        'amount': 150
    }
    order_line_5 = _make_resource(client, 'acol', data)
    order_line_5_ref = get_ref_for_pid('acol', order_line_5.pid)
    assert order_line_5.get('total_amount') == 150
    assert order_line_5.quantity == 1
    assert order_line_5.received_quantity == 0
    assert order_line_5.unreceived_quantity == 1
    assert not order_line_5.is_cancelled
    assert order_line_5.status == AcqOrderLineStatus.APPROVED

    data = {
        'acq_account': {'$ref': get_ref_for_pid('acac', m_serials_acc.pid)},
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'document': {'$ref': get_ref_for_pid('doc', document.pid)},
        'quantity': 10,
        'amount': 7
    }
    order_line_6 = _make_resource(client, 'acol', data)
    order_line_6_ref = get_ref_for_pid('acol', order_line_6.pid)
    assert order_line_6.get('total_amount') == 70
    assert order_line_6.quantity == 10
    assert order_line_6.received_quantity == 0
    assert order_line_6.unreceived_quantity == 10
    assert not order_line_6.is_cancelled
    assert order_line_6.status == AcqOrderLineStatus.APPROVED

    # test order after adding lines
    assert order.get_order_provisional_total_amount() == 715
    assert order.status == AcqOrderStatus.PENDING
    # TODO: fix links to me for the order resource, this should fail
    assert order.can_delete
    assert not order.order_date
    assert order.item_quantity == 24
    assert order.item_received_quantity == 0

    manual_controls = {
        m_root_acc: ((5000, 9285), (0, 0), (0, 715)),
        m_books_acc: ((1550, 1550), (0, 0), (450, 0)),
        m_serials_acc: ((2735, 2735), (0, 0), (265, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    # STEP 3 :: UPDATE ORDER LINES
    #   * Cancel some order lines and change some quantities --> make sure
    #     calculations still good
    #       martigny_books_account:
    #           line_1: quantity: 5 of amount 10   = 50
    #           line_2: quantity: 6 of amount 50   = 300  (quantity: 2 --> 6)
    #           line_3: quantity: 3 of amount 100  = 300 but cancelled !
    #                                        Total = 350
    #       martigny_serials_account:
    #           line_1: quantity: 3 of amount 15   = 45
    #           line_2: quantity: 2 of amount 150  = 300  (quantity: 1 --> 2)
    #           line_3: quantity: 10 of amount 7   = 70  but cancelled
    #                                        Total = 345
    #       Items quantity = 16: order total amount = 695

    order_line_2['quantity'] = 6
    order_line_2.update(order_line_2, dbcommit=True, reindex=True)
    order_line_3['is_cancelled'] = True
    order_line_3.update(order_line_3, dbcommit=True, reindex=True)
    order_line_5['quantity'] = 2
    order_line_5.update(order_line_5, dbcommit=True, reindex=True)
    order_line_6['is_cancelled'] = True
    order_line_6.update(order_line_6, dbcommit=True, reindex=True)

    # ensure correct calculations and status again
    manual_controls = {
        m_root_acc: ((5000, 9305), (0, 0), (0, 695)),
        m_books_acc: ((1650, 1650), (0, 0), (350, 0)),
        m_serials_acc: ((2655, 2655), (0, 0), (345, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    assert order_line_6.get('total_amount') == 70
    assert order_line_6.is_cancelled
    assert order_line_6.quantity == 10
    assert order_line_6.received_quantity == 0
    assert order_line_6.unreceived_quantity == 10
    assert order_line_6.status == AcqOrderLineStatus.CANCELLED

    assert order_line_5.get('total_amount') == 300
    assert not order_line_5.is_cancelled
    assert order_line_5.quantity == 2
    assert order_line_5.received_quantity == 0
    assert order_line_5.unreceived_quantity == 2
    assert order_line_5.status == AcqOrderLineStatus.APPROVED

    assert order_line_4.get('total_amount') == 45
    assert not order_line_4.is_cancelled
    assert order_line_4.quantity == 3
    assert order_line_4.received_quantity == 0
    assert order_line_4.unreceived_quantity == 3
    assert order_line_4.status == AcqOrderLineStatus.APPROVED

    assert order_line_3.get('total_amount') == 300
    assert order_line_3.is_cancelled
    assert order_line_3.quantity == 3
    assert order_line_3.received_quantity == 0
    assert order_line_3.unreceived_quantity == 3
    assert order_line_3.status == AcqOrderLineStatus.CANCELLED

    assert order_line_2.get('total_amount') == 300
    assert not order_line_2.is_cancelled
    assert order_line_2.quantity == 6
    assert order_line_2.received_quantity == 0
    assert order_line_2.unreceived_quantity == 6
    assert order_line_2.status == AcqOrderLineStatus.APPROVED

    assert order_line_1.get('total_amount') == 50
    assert not order_line_1.is_cancelled
    assert order_line_1.quantity == 5
    assert order_line_1.received_quantity == 0
    assert order_line_1.unreceived_quantity == 5
    assert order_line_1.status == AcqOrderLineStatus.APPROVED

    # STEP 4 :: SEND THE ORDER
    #    * Test send order and make sure statuses are up to date.
    #      - check order lines (status, order-date)
    #      - check order (status)
    #      - check notification
    address = vendor_martigny.get('default_contact').get('email')
    emails = [
        {'type': 'to', 'address': address},
        {'type': 'reply_to', 'address': lib_martigny.get('email')}
    ]
    res, data = postdata(
        client,
        'api_order.send_order',
        data=dict(emails=emails),
        url_data=dict(order_pid=order.pid)
    )
    assert res.status_code == 200

    for order_line in [
        {'line': order_line_1, 'status': AcqOrderLineStatus.ORDERED},
        {'line': order_line_2, 'status': AcqOrderLineStatus.ORDERED},
        {'line': order_line_3, 'status': AcqOrderLineStatus.CANCELLED},
        {'line': order_line_4, 'status': AcqOrderLineStatus.ORDERED},
        {'line': order_line_5, 'status': AcqOrderLineStatus.ORDERED},
        {'line': order_line_6, 'status': AcqOrderLineStatus.CANCELLED},
    ]:
        line = AcqOrderLine.get_record_by_pid(order_line.get('line').pid)
        assert line.status == order_line.get('status')
        if line.status == AcqOrderLineStatus.CANCELLED:
            assert not line.order_date
        else:
            assert line.order_date
    # check order
    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.ORDERED
    # notification testing
    notification_pid = data.get('data').get('pid')
    notif = Notification.get_record_by_pid(notification_pid)
    assert notif.organisation_pid == order.organisation_pid
    assert notif.aggregation_key == str(notif.id)
    assert notif.type == NotificationType.ACQUISITION_ORDER
    assert notif.status == NotificationStatus.DONE
    assert notif.acq_order_pid == order.pid
    assert notif.library_pid == order.library_pid
    assert notif.can_be_cancelled() == (False, None)
    assert notif.get_communication_channel() == NotificationChannel.EMAIL
    assert notif.get_language_to_use() == \
        vendor_martigny.get('communication_language')
    assert address in notif.get_recipients(RecipientType.TO)

    # STEP 5 :: CREATE A RECEIPT
    #   * create a receipt without any order lines yet
    #   * but with some adjustments
    ref_acc_book = get_ref_for_pid('acac', m_books_acc.pid)
    ref_acc_serial = get_ref_for_pid('acac', m_serials_acc.pid)
    data = {
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'exchange_rate': 1,
        'amount_adjustments': [
            {
                'label': 'handling fees',
                'amount': 2.0,
                'acq_account': {'$ref': ref_acc_book}
            },
            {
                'label': 'discount',
                'amount': -1.0,
                'acq_account': {'$ref': ref_acc_book}
            },
            {
                'label': 'handling fees',
                'amount': 10,
                'acq_account': {'$ref': ref_acc_serial}
            }
        ],
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)},
        'organisation': {'$ref': get_ref_for_pid('org', org_martigny.pid)}
    }
    receipt_1 = _make_resource(client, 'acre', data)
    assert receipt_1.total_amount == 11  # 2 - 1 + 10
    assert receipt_1.can_delete

    manual_controls = {
        m_root_acc: ((5000, 9294), (0, 11), (0, 695)),
        m_books_acc: ((1649, 1649), (1, 0), (350, 0)),
        m_serials_acc: ((2645, 2645), (10, 0), (345, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    # STEP 6 :: RECEIVE SOME ORDER LINES
    #    Received 2 items from the order_line_1. We need to ensure :
    #    * the order_line status is PARTIALLY_RECEIVED
    #    * the order status is PARTIALLY_RECEIVED too
    #    * the related account amounts are correctly assigned

    # CHECK :: Not possible to receive quantity more than what you ordered
    res, data = postdata(
        client,
        'api_receipt.lines',
        data=[{
            'acq_order_line': {'$ref': order_line_1_ref},
            'amount': 10,
            'quantity': 12,
            'receipt_date': '2021-11-01'
        }],
        url_data=dict(receipt_pid=receipt_1.pid)
    )
    assert res.status_code == 200
    response = data.get('response')
    assert response[0]['status'] == AcqReceiptLineCreationStatus.FAILURE

    # partially receive one order with few quantities in receipt_1
    res, data = postdata(
        client,
        'api_receipt.lines',
        data=[{
            'acq_order_line': {'$ref': order_line_1_ref},
            'amount': 10,
            'quantity': 2,
            'vat_rate': 6,
            'receipt_date': '2021-11-01'
        }],
        url_data=dict(receipt_pid=receipt_1.pid)
    )
    assert res.status_code == 200
    response = data.get('response')
    assert response[0]['status'] == AcqReceiptLineCreationStatus.SUCCESS

    # Test order and order lines
    for order_line in [{
        'line': order_line_1,
        'status': AcqOrderLineStatus.PARTIALLY_RECEIVED,
        'received': 2
    }, {
        'line': order_line_2,
        'status': AcqOrderLineStatus.ORDERED,
        'received': 0
    }, {
        'line': order_line_3,
        'status': AcqOrderLineStatus.CANCELLED,
        'received': 0
    }, {
        'line': order_line_4,
        'status': AcqOrderLineStatus.ORDERED,
        'received': 0
    }, {
        'line': order_line_5,
        'status': AcqOrderLineStatus.ORDERED,
        'received': 0
    }, {
        'line': order_line_6,
        'status': AcqOrderLineStatus.CANCELLED,
        'received': 0
    }]:
        line = AcqOrderLine.get_record_by_pid(order_line.get('line').pid)
        assert line.status == order_line.get('status')
        assert line.received_quantity == order_line.get('received')
    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.PARTIALLY_RECEIVED

    manual_controls = {
        m_root_acc: ((5000, 9292.8), (0, 32.2), (0, 675)),
        m_books_acc: ((1647.8, 1647.8), (22.2, 0), (330, 0)),
        m_serials_acc: ((2645, 2645), (10, 0), (345, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    # STEP 7 :: CREATE NEW RECEIVE AND RECEIVE ALL PENDING ORDER LINES
    #   * Create a second receipt for the same order with no adjustments.
    #   * To to receive all pending order lines BUT with a mistake for
    #     `order_line_5` (try to receive more than ordered items) ==> all lines
    #     except `order_line_5` should have the RECEIVED STATUS
    #   * complete the order reception to receive the `order_line_5`
    data = {
        'exchange_rate': 1,
        'acq_order': {'$ref': get_ref_for_pid('acor', order.pid)},
        'library': {'$ref': get_ref_for_pid('lib', lib_martigny.pid)},
        'organisation': {'$ref': get_ref_for_pid('org', org_martigny.pid)}
    }
    receipt_2 = _make_resource(client, 'acre', data)

    data = [{
        'acq_order_line': {'$ref': order_line_1_ref},
        'amount': 10,
        'quantity': 3,
        'receipt_date': '2021-11-01'
    }, {
        'acq_order_line': {'$ref': order_line_2_ref},
        'amount': 50,
        'quantity': 6,
        'receipt_date': '2021-11-01'
    }, {
        'acq_order_line': {'$ref': order_line_4_ref},
        'amount': 15,
        'quantity': 3,
        'receipt_date': '2021-11-01'
    }, {
        'acq_order_line': {'$ref': order_line_5_ref},
        'amount': 150,
        'quantity': 12,  # too many items ! Max quantity should be 2
        'receipt_date': '2021-11-01'
    }]
    res, data = postdata(
        client,
        'api_receipt.lines',
        data=data,
        url_data=dict(receipt_pid=receipt_2.pid)
    )

    assert res.status_code == 200
    response = data.get('response')
    assert response[0]['status'] == AcqReceiptLineCreationStatus.SUCCESS
    assert response[1]['status'] == AcqReceiptLineCreationStatus.SUCCESS
    assert response[2]['status'] == AcqReceiptLineCreationStatus.SUCCESS
    assert response[3]['status'] == AcqReceiptLineCreationStatus.FAILURE

    # Test order and order lines
    for order_line in [
        {'line': order_line_1, 'status': AcqOrderLineStatus.RECEIVED},
        {'line': order_line_2, 'status': AcqOrderLineStatus.RECEIVED},
        {'line': order_line_3, 'status': AcqOrderLineStatus.CANCELLED},
        {'line': order_line_4, 'status': AcqOrderLineStatus.RECEIVED},
        {'line': order_line_5, 'status': AcqOrderLineStatus.ORDERED},
        {'line': order_line_6, 'status': AcqOrderLineStatus.CANCELLED}
    ]:
        line = AcqOrderLine.get_record_by_pid(order_line.get('line').pid)
        assert line.status == order_line.get('status')

    # Receive the last pending order_line
    data = [{
        'acq_order_line': {'$ref': order_line_5_ref},
        'amount': 150,
        'quantity': 2,
        'receipt_date': '2021-11-01'
    }]
    res, data = postdata(
        client,
        'api_receipt.lines',
        data=data,
        url_data=dict(receipt_pid=receipt_2.pid)
    )
    assert res.status_code == 200
    response = data.get('response')
    assert response[0]['status'] == AcqReceiptLineCreationStatus.SUCCESS
    order_line = AcqOrderLine.get_record_by_pid(order_line_5.pid)
    assert order_line.status == AcqOrderLineStatus.RECEIVED

    # check than order is now fully received
    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.RECEIVED
    # check account amounts
    manual_controls = {
        m_root_acc: ((5000, 9292.8), (0, 707.2), (0, 0)),
        m_books_acc: ((1647.8, 1647.8), (352.2, 0), (0, 0)),
        m_serials_acc: ((2645, 2645), (355, 0), (0, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)

    # TEST 8: DELETE RECEIPTS
    #   * Delete the second receipt. This will also delete the related receipt
    #     lines. The order status must remain to PARTIALLY_RECEIVED.
    #   * Delete the first receipt. The order status should remain to ORDERED
    #     and account amount should be the same than STEP#3.

    # Check links between object
    #   * check some order lines (cancelled or not); not need to test all lines
    #   * check receipt links
    links = order.get_links_to_me(get_pids=True)
    for pid in [order_line_3.pid, order_line_4.pid, order_line_5.pid]:
        assert pid in links['order_lines']
    for pid in [receipt_1.pid, receipt_2.pid]:
        assert pid in links['receipts']

    # DELETE `RECEIPT_2` ----------
    receipt_line_pids = receipt_2.get_receipt_lines(output='pids')
    url = url_for('invenio_records_rest.acre_item', pid_value=receipt_2.pid)
    client.delete(url)
    # Check all resources related to `receipt_2` is deleted
    for pid in receipt_line_pids:
        line = AcqReceiptLine.get_record_by_pid(pid)
        assert line is None
    assert AcqReceipt.get_record_by_pid(receipt_2.pid) is None
    # Check ES is up-to-date
    response = AcqReceiptLinesSearch()\
        .filter('terms', pid=receipt_line_pids).execute()
    assert response.hits.total.value == 0
    response = AcqReceiptsSearch() \
        .filter('term', pid=receipt_2.pid).execute()
    assert response.hits.total.value == 0
    # Check order status
    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.PARTIALLY_RECEIVED

    # DELETE `RECEIPT_1` ----------
    receipt_line_pids = receipt_1.get_receipt_lines(output='pids')
    url = url_for('invenio_records_rest.acre_item', pid_value=receipt_1.pid)
    client.delete(url)
    # Check all resources related to `receipt_1` is deleted
    for pid in receipt_line_pids:
        line = AcqReceiptLine.get_record_by_pid(pid)
        assert line is None
    assert AcqReceipt.get_record_by_pid(receipt_1.pid) is None
    # Check ES is up-to-date
    response = AcqReceiptLinesSearch() \
        .filter('terms', pid=receipt_line_pids).execute()
    assert response.hits.total.value == 0
    response = AcqReceiptsSearch() \
        .filter('term', pid=receipt_1.pid).execute()
    assert response.hits.total.value == 0
    # Check order status
    order = AcqOrder.get_record_by_pid(order.pid)
    assert order.status == AcqOrderStatus.ORDERED

    # ensure correct calculations and status again
    manual_controls = {
        m_root_acc: ((5000, 9305), (0, 0), (0, 695)),
        m_books_acc: ((1650, 1650), (0, 0), (350, 0)),
        m_serials_acc: ((2655, 2655), (0, 0), (345, 0)),
        s_root_acc: ((13500, 20000), (0, 0), (0, 0)),
        s_books_acc: ((2500, 2500), (0, 0), (0, 0)),
        s_serials_acc: ((4000, 4000), (0, 0), (0, 0))
    }
    assert_account_data(manual_controls)