Acumatica · Customization

Acumatica AP Module — Extension Patterns

Acumatica AP Module — Extension Patterns is one of those Acumatica customisations that every team eventually needs and almost no team does well the first time.

John Kihiu12 min read

Accounts Payable is one of the more heavily-touched modules in a typical Acumatica implementation, mostly because every client has an approval process that doesn't quite match the stock configuration. The good news is that AP's extension surface is well-worn: the DACs are stable, the graphs are documented, and most customizations fall into a handful of repeating shapes.

Core DACs and screens

The transactional backbone is APInvoice (bills and adjustments, screen AP301000), APPayment (checks and payments, AP302000), and APAdjust, which links payments back to the invoices they apply against. Vendor master data lives on Vendor (which extends the shared BAccount DAC) and VendorClass. The graph you'll extend most often is APInvoiceEntry, followed by APPaymentEntry for anything touching the payment/application side.

Where the extension hooks live

A PXGraphExtension<APInvoiceEntry> is where you add custom fields via a PXCacheExtension on APInvoice, wire up RowSelecting/RowPersisting handlers, or override actions like Release. Custom approval logic almost always starts with a new field driving a condition in an approval map (Approval Maps screen), rather than hand-rolled code — Acumatica's approval engine already handles multi-level, amount-based, and department-based routing, and reimplementing that in an event handler is the kind of thing that looks clever in the demo and turns into a maintenance headache eighteen months later.

Prefer the approval map over a custom RowPersisting check

If the ask is "invoices over $10,000 need a second approver," that's configuration on the Assignment and Approval Maps screen, bound to a condition on APInvoice. Custom code should only enter the picture when the approval condition depends on something the map can't express — a lookup against an external vendor risk score, for instance.

A realistic scenario: three-way match tolerance

A common AP customization is tightening or loosening the tolerance on PO-to-receipt-to-invoice matching — the client wants a percentage variance allowed on quantity but a hard stop on unit cost. Acumatica's landed cost and PO-linked bill entry already does line-level matching; the extension point is usually a field-level validation on APInvoice/APTran that checks the linked POReceipt line before allowing release.

C# · APInvoiceEntry EXTENSION
public class APInvoiceEntry_MatchTolerance_Extension : PXGraphExtension<APInvoiceEntry>
{
    protected virtual void APTran_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
    {
        var row = (APTran)e.Row;
        if (row == null || row.POLineNbr == null) return;

        var poLine = PXSelect<POLine,
            Where<POLine.orderNbr, Equal<Required<POLine.orderNbr>>,
                And<POLine.lineNbr, Equal<Required<POLine.lineNbr>>>>
            .Select(Base, row.PONbr, row.POLineNbr).RowCast<POLine>().FirstOrDefault();

        if (poLine == null) return;

        decimal variance = Math.Abs((row.UnitCost ?? 0) - (poLine.CuryUnitCost ?? 0));
        if (variance > 0m)
        {
            sender.RaiseExceptionHandling<APTran.unitCost>(row, row.UnitCost,
                new PXSetPropertyException("Unit cost does not match the PO line. Quantity variance is allowed; cost variance is not.", PXErrorLevel.Error));
        }
    }
}

Note this is illustrative — the actual matching fields and the mechanism for pulling the linked PO line depend on the version and whether the invoice was entered against a PO directly or through the Bills and Adjustments screen with the PO Receipt selector; check the AP graph in your target version before assuming field names line up exactly.

Custom 1099 and tax fields

1099 reporting customizations (custom box mapping, non-standard vendor categories) are almost always a PXCacheExtension<Vendor> plus a small change to the 1099 report's data provider, not a change to the core AP posting logic. Keep tax-authority-specific logic (VAT, withholding tax common outside the US) in its own extension rather than layering it onto the 1099 code path — they solve genuinely different problems even though both look like "extra tax fields on the vendor."

Testing and upgrade considerations

APInvoice release triggers GL batch creation, so any RowPersisting handler that throws needs to fail before release, not after — a validation that only surfaces post-release leaves a partially-posted invoice and an inconsistent state. Run your customization project against a copy of the target instance's actual chart of accounts and posting classes before go-live; AP extensions are one of the more common places where a customization tested on demo data passes and then fails against a real vendor class with different terms.

Wrapping up

AP extension work is mostly disciplined use of existing extension points — cache extensions for new fields, graph extensions for validation and event handling, and the approval engine for anything resembling a sign-off workflow. The failure mode to watch for is reaching for custom code before checking whether the approval map, restriction groups, or existing PO matching logic already covers the requirement.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.