Generic Inquiries are the most-shared artifacts in any Acumatica instance — everyone can see them on a dashboard, everyone exports them to Excel — which makes them the most common place data leaks between people who should not see each other's numbers. Salespeople seeing other reps' commissions, branch staff seeing group-wide margins, a warehouse GI exposing cost columns to floor staff. This post is the set of row-level security patterns I actually use, in the order I reach for them.
Start with the security the platform already enforces
A GI runs under the current user's context, so three platform mechanisms apply automatically before you write any conditions:
- Branch security. If a DAC carries a
BranchIDwith the standardPXBranch-aware attributes, rows from branches the user has no access to are filtered by the framework. This is the cheapest row-level security you will ever get — but it only works if the table in your GI is the one carrying the branch column. Join through a projection or a denormalised table and you can lose it. - Restriction groups. Row-Level Security (SM201045 and friends) on entities like GL accounts, subaccounts, customers, and vendors applies inside GI results too, via the
PXAccessmachinery. If a user cannot see account 5000 on the Chart of Accounts, a GI overGLTranhides those rows. - Field-level access rights. Hiding a field by role on Access Rights by Screen affects the GI design screen, not arbitrary GIs built over the DAC. Do not rely on it for GI output.
The pattern that works is: lean on branch security and restriction groups for the entities that support them, and only hand-roll conditions for the gaps.
Pattern 1: conditions on the current user
For "each salesperson sees their own orders" style requirements, the GI Conditions tab can reference the session. The two expressions I use constantly:
-- current user's ID (Users.PKID / Guid)
SOOrder.OwnerID Equals =[AccessInfo.UserID]
-- current user's linked employee / contact
SOOrder.SalesPersonID Equals =[EPEmployee.SalesPersonID]
(with EPEmployee joined on DefContactID = AccessInfo.ContactID)
The gotcha: managers. The moment one person needs to see their team's rows, a flat equality condition collapses into a maintenance headache of per-user GIs. Resolve that with the workgroup tree instead — join EPCompanyTreeMember and filter on membership of the current user's workgroup and its children. It is more joins, but it means org changes are handled on the Company Tree screen by an admin, not by you editing GI conditions.
Pattern 2: restriction groups where they exist
If the requirement maps onto an entity Acumatica already supports in Row-Level Security — GL accounts, subaccounts, customers, vendors, inventory items, warehouses — use restriction groups even if it feels heavier up front. A group named SALES-KE containing the Kenyan customers and the Kenyan sales role gives you enforcement in every GI, report, and screen simultaneously. A GI condition protects one GI; someone will eventually clone the GI and forget the condition. Restriction groups fail closed.
Acumatica's restriction group types (A, B, and their inverse variants) control whether membership grants or revokes visibility and how ungrouped entities behave. Type A with inverse semantics is the one that catches people: an entity in no group is visible to everyone. If your requirement is "deny by default", you must put every sensitive entity into at least one group. Audit this with a GI over the group tables themselves.
Pattern 3: a secured projection DAC
When conditions get too gnarly — say, visibility rules that depend on a custom mapping table — I stop fighting the GI designer and push the security into a PXProjection in a customization project, then build the GI over the projection:
[PXProjection(typeof(
SelectFrom<SOOrder>
.InnerJoin<KGRepTerritory>
.On<KGRepTerritory.salesPersonID.IsEqual<SOOrder.salesPersonID>>
.Where<KGRepTerritory.userID.IsEqual<AccessInfo.userID.FromCurrent>>),
Persistent = false)]
[PXCacheName("Orders (Territory Secured)")]
public class SecuredOrder : PXBqlTable, IBqlTable
{
[PXDBString(2, IsKey = true, BqlField = typeof(SOOrder.orderType))]
public string OrderType { get; set; }
public abstract class orderType : BqlString.Field<orderType> { }
[PXDBString(15, IsKey = true, BqlField = typeof(SOOrder.orderNbr))]
public string OrderNbr { get; set; }
public abstract class orderNbr : BqlString.Field<orderNbr> { }
// ... only the fields the GI is allowed to expose
}
Two benefits: the Where clause travels with the DAC, so every GI built on it inherits the restriction; and the projection only surfaces the fields you declared, so nobody can add the cost column back in the GI designer because it simply is not there. That second property is underrated — it is field-level security by omission.
Anti-patterns I keep removing from client instances
- Hiding columns in the results grid. Export to Excel and OData both ignore visual hiding on older builds. Removal, not hiding.
- Duplicated per-role GIs. Five copies of "Sales Orders — Team X" drift apart within a quarter. One GI plus real security beats five stale ones.
- Securing the dashboard instead of the GI. The dashboard widget respects its own access rights, but the underlying GI screen is still reachable by URL and by search. Secure the data source.
- Forgetting OData. If the GI has Expose via OData ticked, verify the consuming credentials. An integration user with broad rights pointed at a "secured" GI resurrects every row you thought you had hidden.
Wrapping up
Row-level security in GIs is layered: platform branch security and restriction groups first, session-scoped conditions for ownership rules, and a secured PXProjection when the logic outgrows the designer. Whichever layer you use, test as a real restricted user, and re-test after every GI edit — the designer makes it very easy to add a join that silently widens the result set.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.