Tax-Ready Books Check (2025)

Is this client's 2025 file actually ready for the tax preparer? One click answers it: parked and uncategorized balances, duplicate entries, wrong-sign amounts, months that were never fully entered - and a plain-English punch list ending in a READY / NOT YET verdict. Built for the

9 steps · shared by Klajdi · September 3, 2026
Make it your own →
No data is shared in this template. It contains only the recipe — column names and SQL logic. When you run it, your data is processed in your own browser and never leaves your machine.

What data it expects

QBO_GeneralLedger.csv
account_number · VARCHARaccount_type · VARCHARaccount · VARCHARdate · DATEname · VARCHARmemo · VARCHARamount · DOUBLE
QBO_ProfitAndLoss_Month.csv
section · VARCHARaccount · VARCHAR<one column per month, YYYY-MM> · DOUBLE
QBO_TrialBalance.csv
account_number · VARCHARaccount · VARCHARtype · VARCHARdebit · DOUBLEcredit · DOUBLE

Connect QuickBooks Online or drop a CSV / Excel export with a similar layout — the AI adapts the workflow if your columns differ.

How it works — every step, readable

  1. 01Load 2025 general ledger
    SELECT * FROM read_csv_auto('QBO_GeneralLedger.csv', header=true)
  2. 02Load 2025 P&L (account sections)
    SELECT * FROM read_csv_auto('QBO_ProfitAndLoss_Month.csv', header=true)
  3. 03Load 2025 trial balance
    SELECT * FROM read_csv_auto('QBO_TrialBalance.csv', header=true)
  4. 04Check 1 - Parked & uncategorized balances
    SELECT account, COUNT(*) AS transactions, ROUND(SUM(amount), 2) AS total_amount,
      MIN(date) AS first_seen, MAX(date) AS last_seen
    FROM input_1
    WHERE TRY_CAST(date AS DATE) IS NOT NULL AND (
      account ILIKE '%uncategor%' OR account ILIKE '%ask my account%'
      OR account ILIKE '%suspense%' OR account ILIKE '%miscellan%'
      OR account ILIKE '%opening balance%')
    GROUP BY account ORDER BY ABS(SUM(amount)) DESC
  5. 05Check 2 - Duplicate-looking entries
    SELECT account, date, name AS payee, amount, COUNT(*) AS times_recorded,
      ROUND(SUM(amount), 2) AS combined_total
    FROM input_1
    WHERE TRY_CAST(date AS DATE) IS NOT NULL AND amount <> 0
    GROUP BY account, date, name, amount
    HAVING COUNT(*) > 1
    ORDER BY ABS(SUM(amount)) DESC
  6. 06Check 3 - Wrong-sign amounts
    WITH sections AS (SELECT DISTINCT section, account FROM input_2)
    SELECT g.account, s.section, g.date, g.name AS payee, g.amount,
      CASE WHEN s.section ILIKE 'income%'
        THEN 'Negative amount on an income account - customer refund or misposting?'
        ELSE 'Negative amount on an expense account - vendor credit or misposting?' END AS why_flagged
    FROM input_1 g JOIN sections s ON s.account = g.account
    WHERE g.amount < 0 AND (
      s.section ILIKE 'income%' OR s.section ILIKE 'expense%'
      OR s.section ILIKE 'cost of goods%' OR s.section ILIKE 'other expense%')
    ORDER BY g.amount ASC
  7. 07Check 4 - Month-by-month coverage
    WITH m AS (
      SELECT STRFTIME(TRY_CAST(date AS DATE), '%Y-%m') AS month,
        COUNT(*) AS transactions, ROUND(SUM(ABS(amount)), 0) AS total_activity
      FROM input_1 WHERE TRY_CAST(date AS DATE) IS NOT NULL GROUP BY 1),
    med AS (SELECT MEDIAN(transactions) AS typical FROM m)
    SELECT m.month, m.transactions, m.total_activity,
      CASE WHEN m.transactions < 0.25 * (SELECT typical FROM med)
        THEN 'LOW - was this month fully entered and reconciled?' ELSE 'OK' END AS coverage_check
    FROM m ORDER BY m.month
  8. 08Check 5 - Trial balance ties to zero
    SELECT account_number, account, type, debit, credit FROM (
      SELECT 1 AS ord, ROW_NUMBER() OVER () AS rn, CAST(account_number AS VARCHAR) AS account_number, account, type,
        ROUND(COALESCE(debit, 0), 2) AS debit, ROUND(COALESCE(credit, 0), 2) AS credit FROM input_1
      UNION ALL SELECT 2, 0, '', 'TOTAL', '', ROUND(SUM(COALESCE(debit, 0)), 2), ROUND(SUM(COALESCE(credit, 0)), 2) FROM input_1
      UNION ALL SELECT 3, 0, '', 'DIFFERENCE (must be 0.00)', '', ROUND(SUM(COALESCE(debit, 0)) - SUM(COALESCE(credit, 0)), 2), 0.00 FROM input_1
    ) t ORDER BY t.ord, t.rn
  9. 09PUNCH LIST - fix these, then file
    SELECT ord, area, finding, next_step FROM (
    SELECT 1 AS ord, 'COVERAGE' AS area,
      'Books cover ' || (SELECT COUNT(*) FROM input_4) || ' of 12 months of 2025' AS finding,
      CASE WHEN (SELECT COUNT(*) FROM input_4) < 12
        THEN 'Missing months block filing - enter and reconcile them first'
        ELSE 'All 12 months present' END AS next_step
    UNION ALL SELECT 2, 'PARKED BALANCES',
      (SELECT COUNT(*) FROM input_1) || ' uncategorized/suspense account(s) holding $' ||
      COALESCE((SELECT ROUND(SUM(ABS(total_amount)), 0) FROM input_1), 0),
      CASE WHEN (SELECT COUNT(*) FROM input_1) > 0
        THEN 'Recategorize every parked transaction before the preparer sees the books'
        ELSE 'Clean - nothing parked' END
    UNION ALL SELECT 3, 'DUPLICATES',
      (SELECT COUNT(*) FROM input_2) || ' duplicate-looking entries (same date, payee, amount)',
      CASE WHEN (SELECT COUNT(*) FROM input_2) > 0
        THEN 'Review each - delete true duplicates, document the rest'
        ELSE 'None found' END
    UNION ALL SELECT 4, 'WRONG-SIGN AMOUNTS',
      (SELECT COUNT(*) FROM input_3) || ' negative amounts on income/expense accounts',
      CASE WHEN (SELECT COUNT(*) FROM input_3) > 0
        THEN 'Confirm each is a real refund/credit - repost any mispostings'
        ELSE 'None found' END
    UNION ALL SELECT 5, 'UNDER-ENTERED MONTHS',
      (SELECT COUNT(*) FROM input_4 WHERE coverage_check LIKE 'LOW%') || ' month(s) with unusually low activity',
      CASE WHEN (SELECT COUNT(*) FROM input_4 WHERE coverage_check LIKE 'LOW%') > 0
        THEN 'Check bank feeds and reconciliations for those months'
        ELSE 'Activity looks consistent' END
    UNION ALL SELECT 6, 'TRIAL BALANCE',
      'Debits vs credits differ by $' || COALESCE((SELECT ABS(debit) FROM input_5 WHERE account LIKE 'DIFFERENCE%'), 0) || ' (YTD through Dec 2025)',
      CASE WHEN COALESCE((SELECT ABS(debit) FROM input_5 WHERE account LIKE 'DIFFERENCE%'), 0) > 0.005
        THEN 'Books do not balance - resolve before anything else'
        ELSE 'Ties to zero - the handoff artifact is clean' END
    UNION ALL SELECT 7, 'VERDICT',
      CASE WHEN (SELECT COUNT(*) FROM input_1) + (SELECT COUNT(*) FROM input_2) + (SELECT COUNT(*) FROM input_3)
           + (SELECT COUNT(*) FROM input_4 WHERE coverage_check LIKE 'LOW%')
           + CASE WHEN (SELECT COUNT(*) FROM input_4) < 12 THEN 1 ELSE 0 END
           + CASE WHEN COALESCE((SELECT ABS(debit) FROM input_5 WHERE account LIKE 'DIFFERENCE%'), 0) > 0.005 THEN 1 ELSE 0 END = 0
        THEN 'READY - these books look tax-ready. Export and hand off.'
        ELSE 'NOT YET - clear the items above before a clean handoff to the tax preparer.' END,
      'Fix in QuickBooks, then rerun this check in one click'
    ) ORDER BY ord

Run this on your books

Free to try — no sign-up, no card. The workflow runs in your browser; your data never leaves your machine.

Make it your own →