Invoice Builder - Nioron07/Easy-Acumatica GitHub Wiki

This guide covers the InvoiceBuilder, your primary tool for creating the JSON payload needed to create or update AR Invoice records with the InvoicesService.

1. Importing the InvoiceBuilder

To get started, import the InvoiceBuilder from the models directory.

from easy_acumatica.models.invoice_builder import InvoiceBuilder

2. Understanding the Builder's Purpose

When you send data to Acumatica's contract-based API, each field must be wrapped in a specific JSON structure: {"value": ...}. The InvoiceBuilder handles this formatting for you automatically, so you can focus on the data itself.

The builder provides a fluent, chainable interface for setting the fields of an invoice record, including simple fields, line items (Details), and overridden tax lines (TaxDetails).

3. Building an Invoice Payload

Shortcut Methods for Top-Level Fields

For common fields, you can use one of the built-in shortcut methods for a cleaner syntax.

  • .id(note_id) - Sets the top-level id for updating an existing invoice.
  • .type(doc_type) - e.g., 'Invoice', 'Credit Memo'.
  • .customer(customer_id)
  • .date(date_str) - In "YYYY-MM-DD" format.
  • .post_period(period) - e.g., '012025'.
  • .due_date(date_str)
  • .description(text)
  • .hold(True | False)
  • .is_tax_valid(True | False) - Crucial for overriding taxes.
  • .location_id(location)
  • .customer_order(order_nbr)
  • .terms(terms_id)
  • .link_ar_account(account)
  • .bill_to_contact_override(True | False)
  • .ship_to_contact_override(True | False)

Adding Line Items with .add_detail_line()

To add products or services to the invoice, use the add_detail_line method. It requires an inventory_id, quantity, and unit_price, but you can also pass other line-level fields as keyword arguments.

builder = (  
    InvoiceBuilder()  
    .add_detail_line(  
        inventory_id="CONSULTING",  
        quantity=10,  
        unit_price=150.0,  
        TransactionDescription="Initial project consultation"  
    )  
    .add_detail_line(  
        inventory_id="HARDWARE",  
        quantity=2,  
        unit_price=500.0,  
        Branch="PRODWHOLE" # Example of another line-level field  
    )  
)

Overriding Taxes with .add_tax_detail()

To manually specify tax amounts instead of letting Acumatica calculate them, you must first set .is_tax_valid(True) and then add each tax line using add_tax_detail.

builder = (  
    InvoiceBuilder()  
    .is_tax_valid(True) # Required to enable tax override  
    .add_tax_detail(tax_id="GST", taxable_amount=1000.0, tax_amount=50.0)  
    .add_tax_detail(tax_id="PST", taxable_amount=1000.0, tax_amount=70.0)  
)

Specifying Custom Fields with .set_custom_field()

For user-defined or non-standard fields, use set_custom_field.

builder.set_custom_field(  
    view="CurrentDocument",   
    field="TaxZoneID",   
    value="AVALARA"  
)

4. Generating the Final JSON Body

Once you have set all the required fields, call the .to_body() method to generate the final dictionary, which is ready to be sent in your API request.

# Build a complete invoice  
invoice_payload = (  
    InvoiceBuilder()  
    .type("Invoice")  
    .customer("CUST01")  
    .date("2025-01-15")  
    .add_detail_line("ITEM01", 1, 100.0)  
    .is_tax_valid(True)  
    .add_tax_detail("VAT", 100.0, 20.0)  
)

# Get the final dictionary  
json_body = invoice_payload.to_body()

5. Complete Example with InvoicesService

Here is a complete example of how to use the InvoiceBuilder to create a new invoice with custom tax.

from easy_acumatica.models.invoice_builder import InvoiceBuilder  
from easy_acumatica.models.query_builder import QueryOptions

# 1. Build the invoice payload  
invoice_to_create = (  
    InvoiceBuilder()  
    .type("Invoice")  
    .customer("AACUSTOMER")  
    .date("2025-02-10")  
    .description("Invoice with custom tax")  
    .is_tax_valid(True)  
    .add_detail_line("CONSULTING", 5, 120.0)  
    .add_tax_detail("GST", 600.0, 30.0)  
)

# 2. Define options to get the details back in the response  
options = QueryOptions(expand=["Details", "TaxDetails"])

# 3. Use the payload with the InvoicesService to create the record  
try:  
    new_invoice = client.invoices.create_invoice(  
        "24.200.001",  
        builder=invoice_to_create,  
        options=options  
    )  
    print(f"Successfully created invoice: {new_invoice['ReferenceNbr']['value']}")  
except Exception as e:  
    print(f"Failed to create invoice: {e}")