Sales Orders (SO301000) is probably the single most customized screen in a typical Acumatica implementation, and for good reason — it's the point where most companies' actual quirks (custom approval thresholds, unusual discounting, allocation rules that don't match any of Acumatica's stock behavior) have to live somewhere. The graph is SOOrderEntry; header is SOOrder, lines are SOLine. As with Purchase Orders, almost everything about how the screen behaves is governed by SOOrderType.
Order type is the real configuration surface
Before writing a graph extension, check whether Order Type Maintenance (SO201000) already covers the requirement — it controls whether an order requires shipment before invoicing, whether it allocates inventory automatically, credit-limit hold behavior, and which document actions are available. A surprising number of "customization requests" for this screen are actually order-type configuration questions, and treating them as code changes creates maintenance debt that a five-minute configuration change would have avoided.
Key tabs
- Details — the SOLine grid: item, quantity, price (subject to the discount engine), and allocation status.
- Financial — customer terms, currency, and the linked AR customer's credit status.
- Shipping — warehouse, ship-via, and the shipment schedule for orders that ship in multiple parts.
- Commissions — salesperson splits, relevant if commission calculation is in scope for a customization.
Extending SOOrderEntry
The discount engine is the part of this screen most teams eventually need to extend or override — group and line discounts, sourced from Sales Price/Discount tables, don't always match a company's actual pricing rules (tiered pricing by customer attribute, contract-specific overrides). Rather than fighting the built-in engine, most durable customizations add a post-calculation adjustment step that runs after the standard pricing has been applied.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
protected virtual void SOLine_CuryUnitPrice_FieldUpdated(PXCache cache,
PXFieldUpdatedEventArgs e)
{
var line = (SOLine)e.Row;
if (line == null) return;
decimal? contractPrice = GetContractPrice(line.CustomerID, line.InventoryID);
if (contractPrice.HasValue && contractPrice.Value < line.CuryUnitPrice)
cache.SetValue<SOLine.curyUnitPrice>(line, contractPrice.Value);
}
}
Custom fields and tabs via the Customization Project Editor
Adding a field to SOLine is routine; the wrinkle is that SOLine data flows forward into Shipment and Invoice lines via the built-in field-mapping between screens, so a custom field meant to survive that chain needs its own mapping entries (or explicit copy logic in a graph extension) — it won't propagate automatically just because it exists on the source screen.
Common validation and event-handler use cases
Beyond pricing overrides: enforcing a custom approval step for orders discounted below a margin threshold, blocking orders for customers on a custom hold status distinct from the standard credit hold, and validating that a required custom attribute (a project code, a promo code) is present before the order can be completed. All of these hang off RowPersisting/FieldUpdated on the graph extension.
Gotchas
Inventory allocation happens as soon as an order is confirmed for most order types — a customization that assumes stock isn't committed until shipment will misreport available-to-promise quantities. Order type changes mid-lifecycle (converting a quote to a confirmed order, for instance) can reset fields that a customization assumed were stable once set. And the credit limit check reads the customer's current balance at validation time, not at order creation — a customer who goes over their limit due to an unrelated invoice can suddenly block an in-progress order that was fine an hour earlier, which looks like a bug but is the intended behavior.
A large share of "Sales Order customizations" are configuration changes to SO201000 in disguise — required fields, action availability, and hold behavior are all data-driven per order type. Confirm the built-in options don't cover the request before reaching for a graph extension.
Wrapping up
Sales Orders rewards customizations that work with the discount engine and order-type framework rather than around them. Extend pricing after the standard calculation, map custom fields explicitly to downstream shipment and invoice lines, and check configuration options before reaching for code.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.