NetSuite · SuiteQL · Reporting · 15 October 2026

The expenses that weren’t missing

A query concluded that a large share of monthly expenses never reached the income statement. The finance lead didn’t believe it, and was right. The transactions were there. The role running the query just couldn’t see them.

A confident, wrong conclusion

A freight cost review compared carrier bills with what the shipping platforms recorded as label cost. Part of the gap pointed at a company card, so the analysis went looking for card-paid freight in the general ledger and came back with a striking conclusion: a large monthly amount appeared on the card statements but never seemed to reach the P&L.

The finance lead pushed back immediately. He reviews every month-end with the team, and a gap that size would not have gone unnoticed. He was right.

What was actually happening

The SuiteQL ran under an integration role, and that role didn’t have view permission for Credit Card Charge or Credit Card Refund transactions. SuiteQL doesn’t error when a role can’t see a record type. It returns the rows the role can see and quietly leaves out the rest:

SELECT t.type, SUM(tal.amount) AS total
FROM transactionaccountingline tal
JOIN transaction t ON t.id = tal.transaction
WHERE tal.account = ?
  AND t.trandate BETWEEN ? AND ?
GROUP BY t.type
-- no CardChrg rows: not because there are none,
-- because this role can't see them

Once the two permissions were added to the role, the card charges appeared, the expense category reconciled, and the “missing” money was exactly where it belonged.

The rule: an empty result is a claim about what the role can see, not about what exists. Before reporting that something is missing, reconcile the query’s total to a number from a report a full-access user trusts — the P&L line, the balance sheet account, the bank statement.

Why this bites integration roles in particular

Integration and reporting roles are usually built by adding permissions as features need them: customers, items, sales orders, vendor bills. Transaction types that only accounting enters — card charges, card refunds, journal entries in some setups, intercompany transactions, payroll journals — are easy to miss because nothing failed when they were left out. The role worked for every request until the first one that needed them.

The same blind spot shows up in other ways we’ve seen:

A quick permission check before any analysis

Count transactions by type for a recent period under the role you’re about to use, and compare it to the same count run by an administrator:

SELECT t.type, COUNT(*) AS n
FROM transaction t
WHERE t.trandate >= ADD_MONTHS(TRUNC(SYSDATE,'MM'), -1)
GROUP BY t.type
ORDER BY t.type

Any type the administrator sees and the role doesn’t is a gap in the role, and every report built on it inherits that gap.

The broader lesson

The best check in this case wasn’t a query. It was someone who knows the business saying “that can’t be right.” Analysis that contradicts what experienced people see every month deserves a second look at the plumbing before it reaches a conclusion. That’s why we reconcile to the books first and present findings second.

Related: the costs nobody looks at until renewal, and an empty list is not an error message.

New England Systems Group

If there's a process everyone works around instead of through, that's where we start.

SuiteScript development and back-office automation for manufacturers, distributors, producers and retailers. Danbury, Connecticut.

hello@nesystemsgroup.com