Building a Financial Reporting View From Connected Systems
Every Monday at 8:40 the finance lead opens three tabs. Stripe, to see what came in. QuickBooks, to see what the books say. The bank, to see what is actually sitting there. Then a spreadsheet, because none of the three agree, and the number the CEO asks for at 9:00 lives on none of those screens.
That gap is what a financial reporting dashboard is supposed to close. Most attempts fail for the same reason: someone builds a screen first and writes the definitions second. The chart renders, the number is wrong by four percent, and within two months nobody opens it.
This guide is about doing the boring part first. What each system actually knows, where the numbers legitimately disagree, how to profile real data before you design anything, and where a read-only app stops being enough and you have to go back to the ledger.
What a financial reporting dashboard actually has to answer
Before you pick components, write down the questions. In practice a small or mid-sized company has five, and everything else is a drill-down from one of them.
How much cash do we have, and how long does it last at current burn. That is the only number some founders read.
What did we bill, and what did we actually collect. These are different, and the difference is your collections problem.
What is recurring, and is it growing or shrinking. New, expansion, contraction, churn. If you cannot decompose the change, you cannot explain it.
What are we spending, and on what. Payroll separated from everything else, because payroll swamps the rest and hides the movements that matter.
What is stuck. Failed payments in dunning, invoices past due, transactions the bookkeeper has not coded, payouts that have not reconciled.
If your first draft has fourteen tiles and none of them are "what is stuck," it is a decoration, not a reporting tool. The stuck list is the only part anyone acts on.
Where the numbers live, and why they honestly disagree
The billing processor knows charges. The accounting system knows the ledger. The bank knows settled cash. Each is authoritative for something and misleading about everything else, and the disagreements are not bugs.
Stripe shows a charge the second it succeeds. The money is not in your bank for another two days, arrives as one payout covering hundreds of charges, and arrives net of fees. So "revenue" in the processor and "deposits" in the bank will never tie line by line without the payout report in between.
The accounting system shows revenue on an accrual basis if it is set up correctly. An annual plan collected in January is not January revenue, it is one twelfth of January revenue with eleven months of deferred revenue behind it. A cash-basis chart pulled from the processor will show a January spike your accountant will not recognize.
The bank shows movement without meaning. A transfer from your operating account to your reserve account looks exactly like income if you sum credits naively. So does a customer refund reversal. So does a loan draw.
And all three disagree about time. Processors timestamp in UTC. Accounting systems use the entity's local time zone and a posting date that may differ from the transaction date. Banks use a posting date that shifts over a weekend. A "yesterday" filter will return three different sets of rows depending on which system you ask.
The table below is the version of this I would hand a new analyst on day one.
| Question | Ask this system | Why the other sources mislead | How fresh the answer really is |
|---|---|---|---|
| Cash on hand | The bank | The processor shows charges that have not settled; the ledger shows a balance that lags the close | Same day, but pending items move for two to three days |
| Amount invoiced this period | The accounting system | Billing tools miss invoices raised outside the processor; the bank sees nothing until payment lands | Live for issued invoices, not final until the period closes |
| Amount actually collected | The billing processor | The ledger may not have picked up yesterday's payments; the bank shows one payout, not the invoices inside it | Minutes behind, and gross of fees and refunds |
| Recurring revenue, churn, expansion | The billing processor | The ledger flattens subscriptions into revenue lines with no plan, seat or interval detail | Live, but definitions drift, so pick one and write it down |
| Spend by category | The accounting system | Card and bank feeds carry merchant strings, not categories | Only as good as the last coding pass, usually days behind |
| Net revenue after fees, refunds and disputes | The ledger reconciled against processor payout reports | Processor gross charges overstate; the bank sees net payouts with no line detail | Trails the payout cycle by two to five days |
Read the last column carefully. Half the trust problems with a financial reporting dashboard are not accuracy problems, they are freshness problems that nobody labelled. A tile that says $412,880 and a tile that says $412,880 as of the 4:00 UTC sync, before fees, cash basis are the same number with completely different credibility.
The reconciliation you cannot design your way around
At some point you will want one screen showing a customer's subscription, their open invoices and their payments. That requires a join, and the join is where most of these projects quietly rot.
Your processor has customer IDs. Your accounting system has its own customer records, often created by a sync tool, often duplicated because someone typed the company name slightly differently. Your CRM has accounts. Email addresses overlap but do not match, because billing goes to ap@ and the CRM has the founder's address.
You have three honest options.
Pick a spine and accept its gaps. Usually the processor customer ID, carried into the ledger as a custom field. Anything without that ID lands in an unmatched bucket you show on the screen instead of hiding.
Maintain a mapping table in a database you already run, and keep the mapping as data rather than as logic buried in a query. Someone owns it, it gets updated when a duplicate appears, and every view joins through it.
Do not join at all. Show the two systems side by side with a shared filter and let a human do the last mile. This is undignified and it works, and for a company under about a hundred customers it is often the right answer.
What you must not do is fuzzy-match on company name inside a query and present the result as fact. That produces a dashboard that is right for eleven months and catastrophically wrong the month a customer changes legal entity. If you are pulling directly from a warehouse or an application database, the same discipline applies as for any other view over live systems, which is covered in more depth in apps on connected data.
Profile the real data before you design anything
Here is the failure mode that makes AI-built finance screens look amateur. The model imagines the shape of the data instead of looking at it.
It assumes amounts are decimals, and your processor stores integer cents, so every figure comes out a hundred times too large. It assumes one currency, and eleven percent of your rows are EUR, so a naive sum produces a meaningless blend. It picks a bar chart for spend by vendor, and you have four hundred vendors, so the chart is a grey smear. It designs a nice two-line customer cell, and your longest customer name is 94 characters. It assumes amounts are positive, and refunds are stored as negative rows in the same column, so a "total revenue" tile silently nets them and nobody knows.
The fix is unglamorous: run the query, read a profile of what came back, then lay out the screen. Column types. Minimum, maximum, median. Null rate. Distinct count. Longest string. Presence of negatives. Currency spread.
This is exactly why Skopx runs the query and profiles the actual result before it designs an app, rather than designing from the schema alone. A distinct count of 400 on vendor means a ranked table with an "other" row, not a pie chart. A null rate of 30 percent on category means the layout needs an uncategorized bucket on the face of it. There is a longer treatment of these specific visual failures in why AI dashboards look wrong.
Profile before you design, and you skip the entire first round of "this looks great but the numbers are nonsense."
What actually goes on the screen
A layout that survives contact with a real finance team, top to bottom.
A stat grid across the top with four or five figures, each carrying its basis and its as-of time. Cash on hand. Collected month to date. Net recurring revenue. Overdue receivables. Burn. Not fourteen tiles. Four or five that a CEO can read from a phone in a taxi.
A single trend chart underneath. Monthly, twelve to eighteen periods, one series or two. Weekly cash charts look busy and say nothing, because weekly cash movement is dominated by payroll timing.
Then the working sections, which is where the value actually is.
Overdue invoices as a table, sorted by age times amount rather than by amount alone, because a 90-day $8,000 invoice is a worse problem than a 10-day $40,000 one. Columns: customer, amount, days overdue, last contact, owner.
Failed payments in dunning, with the retry state and how many attempts remain. This list converts to cash faster than anything else on the screen and is the one most often missing.
Unreconciled or uncategorized transactions, so the bookkeeping backlog is visible to the person who can chase it rather than buried in the accounting system.
Spend by category with a period filter, plus a variance column against the prior period. A number without a comparison is trivia.
Filters at the top: period, entity if you have more than one, and currency basis. Global filters that every section respects. Per-section filters are a promise you will break.
The Skopx component set covers this directly: metric and stat grid for the top row, chart for the trend, table and list for the working sections, section and divider for structure, callout for a standing note like "figures are cash basis, the books close on the 12th," filter for the controls, and action button for the handful of things a person should be able to do without leaving the screen.
What this kind of app can and cannot do
This is the part to be blunt about, because it determines whether you are building the right thing.
An app built on connected systems reads. It can query a database read-only, pull from connected tools, and take an explicit action through a connected tool when someone clicks a button and confirms. Skopx apps do exactly that and no more. They do not store their own records, and there is no form component that creates new data.
For financial reporting that boundary lands in a specific place, and it is worth knowing before you start.
Things that work well. Reading billing, ledger and bank data into one view. Ranking, comparing and filtering it. Flagging exceptions. Clicking a button that emails a customer about an overdue invoice through Gmail, or posts the weekly summary into a Slack channel, or opens a task in Jira or Notion to investigate a variance. Each of those is an explicit click with a confirmation, which is what you want when the action is a payment reminder to a customer.
Things that do not work this way. You cannot record a manual accrual in the app. You cannot key a journal entry. You cannot let a controller type an adjustment that lives only in the dashboard, because there is nowhere for it to live. You cannot build an invoicing product on this, because the invoice has to be created and owned in the system of record.
That is not a limitation to route around with a clever hack. It is the correct division of labor. Your ledger is the system of record, it has an audit trail, and finance regulators and auditors care about that trail. A reporting view sitting on top of it should read and it should notify. When an adjustment is needed, make it in the accounting system and let the view refresh.
If you do need human overrides visible on the screen, for example a manual classification of one-off revenue, keep them as a table in a database you already run, maintained by whoever owns it, and read that table into the app alongside everything else. The record still lives in a system with a history. The trade-offs between this approach and generating a full custom application are worth understanding before you commit, and are laid out in declarative apps versus generated code.
Who gets to see which number
Financial data is the clearest case in internal tooling where "everyone in the company" is the wrong default.
Company-level revenue and cash: typically the leadership team, sometimes everyone, and that is a deliberate cultural choice rather than a technical one.
Per-customer contract values: sales and finance. If the whole company sees which accounts pay the most, that information leaves with the next person who leaves.
Payroll detail: a very short list. If your spend-by-category view includes a payroll line broken out by person, it is not a company dashboard any more.
Bank balances and account details: finance only, and be careful about what the underlying query returns even if the screen does not display it. If a masked account number is in the result set, treat it as exposed.
Two practical rules. Filter at the query, not at the render, so restricted rows never reach the browser. And keep the sensitive version of the view separate from the company version rather than trying to make one screen behave differently for different viewers, because conditional visibility is where these mistakes hide. Apps built this way are either private to you or shared with your organization, so the clean pattern is two apps: a leadership one and a broader one that stops at aggregates. Published research on AI-assisted application building has repeatedly found deployed apps shipping without row-level protections on the database underneath, which is a reminder that the guardrail has to sit at the data layer. More on drawing those lines in internal tools permissions.
Making it survive month two
Most internal reporting views die between the fourth and eighth week. The pattern is predictable.
Someone notices a number is off. Nobody can say what the number means, because the definition was never written down. Trust drops to zero and the spreadsheet comes back.
Four habits prevent this.
Write the definition next to the number. Not in a wiki. On the screen, in a callout or in the metric's own subtitle. "Net revenue, cash basis, after processor fees and refunds, excludes internal transfers." Three lines of text at the top of a financial reporting dashboard prevent more arguments than any amount of chart polish.
Stamp the freshness. Every section says when its data was last read. Different sources have different lags and pretending otherwise is how a Monday screen shows Friday's bank balance next to this morning's charges.
Show the exceptions rather than smoothing them. Unmatched customers, uncategorized spend, transactions in a currency you cannot convert. If those are silently dropped, your totals are wrong by a quiet amount. If they are on the screen as a count, someone fixes them.
Push the summary to where people already are. A screen that requires remembering to open it gets opened for two weeks. A scheduled job that posts the three headline figures and the overdue list into a finance channel every Monday at 8:00 keeps working. Skopx workflows can run that on a schedule with retries and run history, and the app stays as the place you go when the summary raises a question.
One more, easily missed: agree what happens at close. If the books close on the 12th, then between the 1st and the 12th your prior-month figures are moving. Either label them as provisional or freeze the prior month once close is done. Numbers that change after people have quoted them do more damage than numbers that were never published.
When a spreadsheet is still the better answer
Not every finance question deserves an app, and pretending otherwise wastes a month.
If the question is asked once a quarter, a spreadsheet is faster to build and easier to adjust.
If the work is genuinely modelling, scenarios, a three-statement forecast, a cap table, then a spreadsheet is not a legacy tool, it is the right tool, because the logic changes every time you use it.
If you have fewer than about thirty invoices a month, the overdue list is a glance in the accounting system.
Where a persistent view earns its place is the recurring, multi-source, multi-person case: the same five questions every week, data from three systems that will not join themselves, and more than one person needing the answer without asking finance. That is also the moment the same reasoning applies to adjacent screens, and the patterns carry over to an executive dashboard or an operations dashboard built on the same connections.
FAQ
How accurate can a financial reporting dashboard built on live connections actually be?
Accurate to the source, which is the honest ceiling. It will match your billing processor to the row and your bank to the balance. It will not match your closed books during the period, because the books are not closed yet, and any view that claims to is hiding accruals, deferrals and reclassifications that a human still makes. Treat it as the operating view and the closed ledger as the audited one. Label it that way on the screen and you will avoid most of the arguments.
Can I build a full invoicing or bookkeeping system this way?
No, and you should not try. An app of this kind reads from connected systems and takes explicit actions through connected tools. It has no storage of its own and no form component to create records. That rules out being the system of record for invoices, journal entries or payments. Keep the ledger in the accounting system and build the reporting view on top. The same limit shapes what is realistic for other read-and-act tools, for example a support dashboard or a CRM dashboard.
How do I handle multiple currencies without producing a meaningless total?
Decide three things before you write the query. The reporting currency. The rate source. And the rate date, transaction date or period-end. Then convert in the query, not in the chart, and always show the original currency in any detail table. A total that silently sums mixed currencies is worse than no total, because it looks fine. If you cannot get a defensible rate, show the currencies as separate rows rather than one blended number.
What is the fastest thing to put on the screen first?
The stuck list. Overdue invoices ranked by age times amount, and failed payments still in dunning. It is one query per source, it needs no reconciliation between systems, and it turns into collected cash within days. Cash and revenue trend tiles feel more important and change nobody's behaviour in week one.
How often should the data refresh?
Match the refresh to the decision. Cash and collections daily is plenty. Recurring revenue and spend by category weekly, because they do not move meaningfully faster than that. Anything closer to real time invites people to watch a number instead of acting on it, and it multiplies load on the databases and APIs underneath for no gain. The exception is the failed-payment list, which is worth reading more often because the retry window is short.
Should the same screen serve the CEO and the bookkeeper?
Usually not. The CEO wants five figures and a trend. The bookkeeper wants three hundred rows they can work through. Those are different layouts and often different permissions. Build both, share the definitions between them, and keep the sensitive detail out of the version that goes to the whole leadership team.
The short version
Write the definitions before you draw anything. Accept that billing, the ledger and the bank disagree for legitimate reasons, and say which one each figure comes from. Profile the real data so the layout fits what actually came back. Put the stuck list on the screen, because it is the part that converts to cash. Keep the ledger as the system of record and let the view read and notify.
Do that, and the Monday morning three-tab ritual turns into one screen that survives past month two. Skip it, and you have built a very attractive spreadsheet nobody trusts.
Skopx Team
The Skopx engineering and product team