def create_contact_person():
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)

    contact_person = ContactPerson(
        first_name="John",
        last_name="Smith",
        email_address="*****@*****.**",
        include_in_emails=True,
    )
    contact = Contact(
        name="FooBar",
        first_name="Foo",
        last_name="Bar",
        email_address="*****@*****.**",
        contact_persons=[contact_person],
    )
    contacts = Contacts(contacts=[contact])
    try:
        created_contacts = accounting_api.create_contacts(
            xero_tenant_id, contacts=contacts)  # type: Contacts
    except AccountingBadRequestException as exception:
        sub_title = "Error: " + exception.reason
        code = jsonify(exception.error_data)
    else:
        sub_title = "Contact {} created.".format(
            getvalue(created_contacts, "contacts.0.name", ""))
        code = serialize_model(created_contacts)

    return render_template("code.html",
                           title="Create Contacts",
                           code=code,
                           sub_title=sub_title)
Example #2
0
def get_journals(since: Optional[datetime.datetime] = None):
    if since is None:
        since = datetime.datetime.now() - relativedelta(days=90)
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)
    these = accounting_api.get_journals(xero_tenant_id=xero_tenant_id,
                                        if_modified_since=since)
    while these and these.journals:
        yield from these.journals
        offset = these.journals[-1].journal_number
        these = accounting_api.get_journals(xero_tenant_id=xero_tenant_id,
                                            if_modified_since=since,
                                            offset=offset)
def get_invoices():
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)

    invoices = accounting_api.get_invoices(xero_tenant_id,
                                           statuses=["DRAFT", "SUBMITTED"])
    code = serialize_model(invoices)
    sub_title = "Total invoices found: {}".format(len(invoices.invoices))

    return render_template("code.html",
                           title="Invoices",
                           code=code,
                           sub_title=sub_title)
Example #4
0
def journals_list():
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)
    journals = accounting_api.get_journals(
        xero_tenant_id=xero_tenant_id,
        if_modified_since=datetime.datetime.now() - relativedelta(days=90))

    return render_template(
        "code.html",
        title="Journals",
        code=serialize_model(journals),
        sub_title="For Past 90 days",
    )
Example #5
0
def members_list():
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)

    sources = defaultdict(list)
    member_transactions = defaultdict(list)

    # Walk the full journal for the past 90 days for membership items
    # and split them based on which type they are, i.e. 'CASHREC' / 'ACCREC'

    for journal in get_journals(datetime.datetime.now() -
                                relativedelta(days=90)):
        if is_membership_journal(journal):
            sources[journal.source_type].append(journal.source_id)

    transactions = []
    for source, source_ids in sources.items():

        if source == 'CASHREC':
            for s in source_ids:
                transactions.extend(
                    # get_bank_transactions has to be done individually but there's usually not many of them
                    accounting_api.get_bank_transaction(xero_tenant_id,
                                                        s).bank_transactions)
        elif source == 'ACCREC':
            transactions.extend(
                accounting_api.get_invoices(xero_tenant_id,
                                            i_ds=source_ids).invoices)
        else:
            raise ValueError(source)

    for transaction in transactions:
        c = transaction.contact.contact_id
        member_transactions[c].append(transaction)

    members = accounting_api.get_contacts(
        xero_tenant_id, i_ds=list(member_transactions.keys())).contacts
    contact_sheet = []
    for m in members:
        contact_sheet.append({
            **fix_contact(m), 'transactions':
            len(member_transactions[m.contact_id])
        })

    return render_template(
        "code.html",
        title="Members",
        code=serialize_model(contact_sheet),
        sub_title="For Past 90 days",
    )
def test_get_accounts(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    expected = Accounts(accounts=[
        Account(
            account_id="ebd06280-af70-4bed-97c6-7451a454ad85",
            bank_account_number="0209087654321050",
            bank_account_type="BANK",
            code="091",
            currency_code=CurrencyCode.NZD,
            enable_payments_to_account=False,
            has_attachments=False,
            name="Business Savings Account",
            tax_type="NONE",
            type=AccountType.BANK,
        ),
        Account(
            account_id="7d05a53d-613d-4eb2-a2fc-dcb6adb80b80",
            code="200",
            description="Income from any normal business activity",
            enable_payments_to_account=False,
            has_attachments=False,
            name="Sales",
            tax_type="OUTPUT2",
            type=AccountType.REVENUE,
        ),
    ])
    # When getting all accounts
    result = sandbox_accounting_api.get_accounts(xero_tenant_id)
    # Then expect all accounts to be received
    assert result == expected
def test_delete_account(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    account_id = "7f3c0bec-f3e7-4073-b4d6-cc56dd027ef1"
    expected = Accounts(accounts=[
        Account(
            _class="EXPENSE",
            account_id=account_id,
            code="123456",
            description="Hello World",
            enable_payments_to_account=False,
            has_attachments=False,
            name="FooBar",
            reporting_code="EXP",
            reporting_code_name="Expense",
            show_in_expense_claims=False,
            status="DELETED",
            tax_type="INPUT",
            type=AccountType.EXPENSE,
            updated_date_utc=datetime.datetime(
                2019, 2, 22, 1, 16, 57, 210000, tzinfo=tz.UTC),
        )
    ])
    # When deleting account
    result = sandbox_accounting_api.delete_account(xero_tenant_id, account_id)
    # Then expect the account to be deleted
    assert result == expected
def test_update_account(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    account_id = "99ce6032-0678-4aa0-8148-240c75fee33a"
    expected = accounts = Accounts(accounts=[
        Account(
            _class="EXPENSE",
            account_id=account_id,
            code="654321",
            description="Good Bye World",
            enable_payments_to_account=False,
            has_attachments=False,
            name="BarFoo",
            reporting_code="EXP",
            reporting_code_name="Expense",
            show_in_expense_claims=False,
            status="ACTIVE",
            tax_type="INPUT",
            type=AccountType.EXPENSE,
            updated_date_utc=datetime.datetime(
                2019, 2, 22, 0, 29, 49, 333000, tzinfo=tz.UTC),
        )
    ])
    # When updating account
    result = sandbox_accounting_api.update_account(xero_tenant_id, account_id,
                                                   accounts)
    # Then expect the account to be updated
    assert result == expected
def test_create_invoice_history(sandbox_accounting_api: AccountingApi,
                                xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    invoice_id = "d4956132-ed94-4dd7-9eaa-aa22dfdf06f2"
    history_records = HistoryRecords([
        HistoryRecord(
            details="Received through the Xero API from ABC Org",
            changes="Approved",
            user="******",
            date_utc=datetime.datetime(2018, 2, 28, 21, 2, 11, tzinfo=tz.UTC),
        ),
        HistoryRecord(
            details="INV-0041 to ABC Furniture for 100.00.",
            changes="Edited",
            user="******",
            date_utc=datetime.datetime(2018, 2, 28, 21, 1, 29, tzinfo=tz.UTC),
        ),
    ])
    # When creating invoice history
    result = sandbox_accounting_api.create_invoice_history(
        xero_tenant_id, invoice_id, history_records)
    # Then expect created invoice history records
    expected = HistoryRecords(
    )  # todo confirm empty response from sandbox is correct
    assert result == expected
def test_get_account(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    account_id = "99ce6032-0678-4aa0-8148-240c75fee33a"
    expected = Accounts(accounts=[
        Account(
            _class="EXPENSE",
            account_id=account_id,
            code="123456",
            description="Hello World",
            enable_payments_to_account=False,
            has_attachments=False,
            name="FooBar",
            reporting_code="EXP",
            reporting_code_name="Expense",
            show_in_expense_claims=False,
            status="ACTIVE",
            tax_type="INPUT",
            type=AccountType.EXPENSE,
            updated_date_utc=datetime.datetime(
                2019, 2, 22, 1, 2, 39, 120000, tzinfo=tz.UTC),
        )
    ])
    # When getting account
    result = sandbox_accounting_api.get_account(xero_tenant_id, account_id)
    # Then expect one account to be received
    assert result == expected
def test_email_invoice(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    invoice_id = "d4956132-ed94-4dd7-9eaa-aa22dfdf06f2"
    request_empty = RequestEmpty()
    # When email invoice
    result = sandbox_accounting_api.email_invoice(xero_tenant_id, invoice_id,
                                                  request_empty)
    # Then expect email invoiced
    assert result is None, "email_invoice doesn't return any data"
Example #12
0
def tenants():
    identity_api = IdentityApi(api_client)
    accounting_api = AccountingApi(api_client)

    available_tenants = []
    for connection in identity_api.get_connections():
        tenant = serialize(connection)
        if connection.tenant_type == "ORGANISATION":
            organisations = accounting_api.get_organisations(
                xero_tenant_id=connection.tenant_id)
            tenant["organisations"] = serialize(organisations)

        available_tenants.append(tenant)

    return render_template(
        "code.html",
        title="Xero Tenants",
        code=json.dumps(available_tenants, sort_keys=True, indent=4),
    )
def test_get_invoice_history(sandbox_accounting_api: AccountingApi,
                             xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test invoice data
    invoice_id = "d4956132-ed94-4dd7-9eaa-aa22dfdf06f2"
    # When getting invoice history
    result = sandbox_accounting_api.get_invoice_history(
        xero_tenant_id, invoice_id)
    # Then expect invoice history to be received
    expected = HistoryRecords(
    )  # todo confirm empty response from sandbox is correct
    assert result == expected
Example #14
0
def create_multiple_contacts():
    xero_tenant_id = get_xero_tenant_id()
    accounting_api = AccountingApi(api_client)

    contact = Contact(
        name="George Jetson",
        first_name="George",
        last_name="Jetson",
        email_address="*****@*****.**",
    )
    # Add the same contact twice - the first one will succeed, but the
    # second contact will fail with a validation error which we'll show.
    contacts = Contacts(contacts=[contact, contact])
    try:
        created_contacts = accounting_api.create_contacts(
            xero_tenant_id, contacts=contacts,
            summarize_errors=False)  # type: Contacts
    except AccountingBadRequestException as exception:
        sub_title = "Error: " + exception.reason
        result_list = None
        code = jsonify(exception.error_data)
    else:
        sub_title = ""
        result_list = []
        for contact in created_contacts.contacts:
            if contact.has_validation_errors:
                error = getvalue(contact.validation_errors, "0.message", "")
                result_list.append("Error: {}".format(error))
            else:
                result_list.append("Contact {} created.".format(contact.name))

        code = serialize_model(created_contacts)

    return render_template(
        "code.html",
        title="Create Multiple Contacts",
        code=code,
        result_list=result_list,
        sub_title=sub_title,
    )
def test_get_organisations(accounting_api: AccountingApi, xero_tenant_id):
    """
    Test AccountingApi.get_organisations()
    """
    # Given correct api client and xero tenant id
    # When fetching organisations
    response = accounting_api.get_organisations(xero_tenant_id=xero_tenant_id)
    # Then expect correct response with organisations tenant has access to
    assert isinstance(response, models.Organisations)
    organisations = response.organisations
    assert isinstance(organisations, list)
    assert len(organisations) == 1
    org = organisations[0]
    assert isinstance(org, models.Organisation)
    assert org.organisation_id
    assert org.is_demo_company
def test_get_account_attachments(sandbox_accounting_api: AccountingApi,
                                 xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test data
    account_id = "7f3c0bec-f3e7-4073-b4d6-cc56dd027ef1"
    expected = Attachments(attachments=[
        Attachment(
            attachment_id="52a643be-cd5c-489f-9778-53a9fd337756",
            content_length=Decimal("2878711"),
            file_name="sample5.jpg",
            include_online=None,
            mime_type="image/jpg",
            url="https://api.xero.com/api.xro/2.0/Accounts/"
            "da962997-a8bd-4dff-9616-01cdc199283f/Attachments/sample5.jpg",
        )
    ])
    # When getting account attachments
    result = sandbox_accounting_api.get_account_attachments(
        xero_tenant_id, account_id)
    # Then expect the account attachments to be received
    assert result == expected
def test_create_account(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test account data
    expected = Account(
        _class="EXPENSE",
        account_id="66b262e2-561e-423e-8937-47d558f13442",
        code="123456",
        description="Hello World",
        enable_payments_to_account=False,
        has_attachments=False,
        name="Foobar",
        reporting_code="EXP",
        reporting_code_name="Expense",
        show_in_expense_claims=False,
        status="ACTIVE",
        tax_type="INPUT",
        type=AccountType.EXPENSE,
        updated_date_utc=datetime.datetime(2019,
                                           2,
                                           21,
                                           23,
                                           59,
                                           9,
                                           320000,
                                           tzinfo=tz.UTC),
    )
    account = Account(
        code=expected.code,
        name=expected.name,
        type=expected.type,
        bank_account_number=None,  # required but not set in sandbox version
    )
    # When creating account
    result = sandbox_accounting_api.create_account(xero_tenant_id, account)
    # Then expect new account to be created
    assert isinstance(result, Accounts)
    assert len(result.accounts) == 1
    result_account = result.accounts[0]
    assert result_account == expected
def accounting_api(api_client):
    return AccountingApi(api_client=api_client)
def test_get_invoices(sandbox_accounting_api: AccountingApi, xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test invoices data
    # When getting all invoices
    result = sandbox_accounting_api.get_invoices(xero_tenant_id)
    # Then expect correct invoices received
    expected = Invoices(invoices=[
        Invoice(
            amount_credited=Decimal("0.00"),
            amount_due=Decimal("0.00"),
            amount_paid=Decimal("0.00"),
            contact=Contact(
                contact_id="a3675fc4-f8dd-4f03-ba5b-f1870566bcd7",
                has_attachments=False,
                has_validation_errors=False,
                name="Barney Rubble-83203",
                addresses=[],
                contact_groups=[],
                contact_persons=[],
                phones=[],
            ),
            credit_notes=[],
            line_items=[],
            overpayments=[],
            payments=[],
            prepayments=[],
            currency_code=CurrencyCode.NZD,
            currency_rate=Decimal("1.000000"),
            date=datetime.date(2018, 10, 20),
            due_date=datetime.date(2018, 12, 30),
            has_attachments=False,
            has_errors=False,
            invoice_id="d4956132-ed94-4dd7-9eaa-aa22dfdf06f2",
            invoice_number="INV-0001",
            is_discounted=False,
            line_amount_types=LineAmountTypes.EXCLUSIVE,
            reference="Red Fish, Blue Fish",
            sent_to_contact=True,
            status="VOIDED",
            sub_total=Decimal("40.00"),
            total=Decimal("40.00"),
            total_tax=Decimal("0.00"),
            type="ACCREC",
            updated_date_utc=datetime.datetime(
                2018, 11, 2, 16, 31, 30, 160000, tzinfo=tz.UTC),
        ),
        Invoice(
            amount_credited=Decimal("0.00"),
            amount_due=Decimal("0.00"),
            amount_paid=Decimal("46.00"),
            contact=Contact(
                contact_id="a3675fc4-f8dd-4f03-ba5b-f1870566bcd7",
                has_attachments=False,
                has_validation_errors=False,
                name="Barney Rubble-83203",
                addresses=[],
                contact_groups=[],
                contact_persons=[],
                phones=[],
            ),
            credit_notes=[],
            line_items=[],
            overpayments=[],
            prepayments=[],
            currency_code=CurrencyCode.NZD,
            currency_rate=Decimal("1.000000"),
            date=datetime.date(2018, 10, 20),
            due_date=datetime.date(2018, 12, 30),
            fully_paid_on_date=datetime.date(2018, 11, 29),
            has_attachments=False,
            has_errors=False,
            invoice_id="046d8a6d-1ae1-4b4d-9340-5601bdf41b87",
            invoice_number="INV-0002",
            is_discounted=False,
            line_amount_types=LineAmountTypes.EXCLUSIVE,
            payments=[
                Payment(
                    amount=Decimal("46.00"),
                    currency_rate=Decimal("1.000000"),
                    date=datetime.date(2018, 11, 29),
                    has_account=False,
                    has_validation_errors=False,
                    payment_id="99ea7f6b-c513-4066-bc27-b7c65dcd76c2",
                )
            ],
            reference="Red Fish, Blue Fish",
            sent_to_contact=True,
            status="PAID",
            sub_total=Decimal("40.00"),
            total=Decimal("46.00"),
            total_tax=Decimal("6.00"),
            type="ACCREC",
            updated_date_utc=datetime.datetime(
                2018, 11, 2, 16, 36, 32, 690000, tzinfo=tz.UTC),
        ),
        Invoice(
            amount_credited=Decimal("0.00"),
            amount_due=Decimal("115.00"),
            amount_paid=Decimal("0.00"),
            contact=Contact(
                contact_id="a3675fc4-f8dd-4f03-ba5b-f1870566bcd7",
                has_attachments=False,
                has_validation_errors=False,
                name="Barney Rubble-83203",
                addresses=[],
                contact_groups=[],
                contact_persons=[],
                phones=[],
            ),
            credit_notes=[],
            line_items=[],
            overpayments=[],
            payments=[],
            prepayments=[],
            currency_code=CurrencyCode.NZD,
            currency_rate=Decimal("1.000000"),
            date=datetime.date(2018, 11, 2),
            due_date=datetime.date(2018, 11, 7),
            has_attachments=False,
            has_errors=False,
            invoice_id="7ef31b20-de17-4312-8382-412f869b1510",
            invoice_number="INV-0003",
            is_discounted=False,
            line_amount_types=LineAmountTypes.EXCLUSIVE,
            reference="",
            status="AUTHORISED",
            sub_total=Decimal("100.00"),
            total=Decimal("115.00"),
            total_tax=Decimal("15.00"),
            type="ACCREC",
            updated_date_utc=datetime.datetime(
                2018, 11, 2, 16, 37, 28, 927000, tzinfo=tz.UTC),
        ),
    ])
    assert result == expected
def sandbox_accounting_api(api_client):
    return AccountingApi(
        api_client=api_client,
        base_url="https://xero-accounting.getsandbox.com/api.xro/2.0",
    )
def test_update_or_create_invoices(sandbox_accounting_api: AccountingApi,
                                   xero_tenant_id):
    # Given sandbox API, tenant id, and hardcoded test invoices data
    expected = invoices = Invoices(invoices=[
        Invoice(
            amount_due=Decimal("40.00"),
            amount_paid=Decimal("0.00"),
            contact=Contact(
                addresses=[
                    Address(
                        address_type="STREET",
                        attention_to="",
                        city="",
                        country="",
                        postal_code="",
                        region="",
                    ),
                    Address(
                        address_type="POBOX",
                        attention_to="",
                        city="Anytown",
                        country="USA",
                        postal_code="10101",
                        region="NY",
                    ),
                ],
                bank_account_details="",
                contact_groups=[
                    ContactGroup(
                        contact_group_id="17b44ed7-4389-4162-91cb-3dd5766e4e22",
                        contacts=[],
                        name="Oasis",
                        status="ACTIVE",
                    )
                ],
                contact_id="430fa14a-f945-44d3-9f97-5df5e28441b8",
                contact_persons=[
                    ContactPerson(
                        email_address="*****@*****.**",
                        first_name="Debbie",
                        include_in_emails=False,
                        last_name="Gwyther",
                    )
                ],
                contact_status="ACTIVE",
                email_address="*****@*****.**",
                first_name="Liam",
                has_attachments=False,
                has_validation_errors=False,
                is_customer=True,
                is_supplier=True,
                last_name="Gallagher",
                name="Liam Gallagher",
                phones=[
                    Phone(
                        phone_area_code="212",
                        phone_country_code="",
                        phone_number="222-2222",
                        phone_type="DEFAULT",
                    ),
                    Phone(
                        phone_area_code="",
                        phone_country_code="",
                        phone_number="",
                        phone_type="DDI",
                    ),
                    Phone(
                        phone_area_code="212",
                        phone_country_code="",
                        phone_number="333-2233",
                        phone_type="FAX",
                    ),
                    Phone(
                        phone_area_code="212",
                        phone_country_code="",
                        phone_number="444-3433",
                        phone_type="MOBILE",
                    ),
                ],
                purchases_tracking_categories=[],
                sales_tracking_categories=[],
                updated_date_utc=datetime.datetime(
                    2019, 3, 5, 0, 54, 41, 53000, tzinfo=tz.UTC),
            ),
            currency_code=CurrencyCode.NZD,
            currency_rate=Decimal("1.000000"),
            date=datetime.date(2019, 3, 11),
            due_date=datetime.date(2018, 12, 10),
            has_attachments=False,
            has_errors=False,
            invoice_id="ed255415-e141-4150-aab7-89c3bbbb851c",
            invoice_number="INV-0007",
            is_discounted=False,
            line_amount_types=LineAmountTypes.EXCLUSIVE,
            line_items=[
                LineItem(
                    account_code="200",
                    description="Acme Tires",
                    line_amount=Decimal("40.00"),
                    line_item_id="5f7a612b-fdcc-4d33-90fa-a9f6bc6db32f",
                    quantity=Decimal("2.0000"),
                    tax_amount=Decimal("0.00"),
                    tax_type="NONE",
                    tracking=[],
                    unit_amount=Decimal("20.00"),
                )
            ],
            overpayments=[],
            prepayments=[],
            reference="Website Design",
            sent_to_contact=False,
            status="AUTHORISED",
            status_attribute_string="OK",
            sub_total=Decimal("40.00"),
            total=Decimal("40.00"),
            total_tax=Decimal("0.00"),
            type="ACCREC",
            updated_date_utc=datetime.datetime(
                2019, 3, 11, 17, 58, 46, 117000, tzinfo=tz.UTC),
        )
    ])
    # When updating or creating invoices
    result = sandbox_accounting_api.update_or_create_invoices(
        xero_tenant_id, invoices)
    # Then expect invoices to be created or updated
    assert result == expected