How we compute each finance indicator
For every figure: what it measures, its formula, the Odoo models read, the scope, the convention used and its known limits.
Why this page exists#
An accountant reviewing our figures does not ask whether the tool looks good. They ask where the amount comes from, over which period, under which convention, and what is not in it. Without those answers they cannot extend their trust, so they withhold it. This page exists so that nothing has to be guessed, and it is public: no account is needed to read it.
What keeps it current#
These definitions are not written alongside the product, they live in the code that computes them. One automated check refuses any change to an indicator's calculation that does not come with its definition, a second refuses this page drifting from the definitions, and a third refuses a definition without its translation. Unguarded documentation is wrong within three months; this one cannot be without blocking the release.
Two consequences worth stating:
- A figure's scope is measured, not declared. When UpBoard reports "posted entries from 1 to 13 October, 1,240 lines read", those values come from the reads actually executed in your Odoo, not from a sentence written by hand.
- Only posted entries are read, with one exception flagged as such: margin by product, which reads confirmed sales orders and is therefore not an accounting read.
The three rules that govern periods#
They apply to every dated indicator, and they are the ones that go wrong most silently elsewhere.
- A period is determined by the entry's accounting date, never by its creation date or its last technical modification. For receivables and payables it is the due date that counts, since what is measured is lateness.
- Cumulative figures start at the opening of your fiscal year, not on 1 January. If your year ends on 30 June, the comparison with the previous year and the corporate tax prepayments follow that year.
- The state of your books is read from Odoo. A month earlier than your lock date is reported as final; an open month states its day and the lock date. If UpBoard cannot read that date, it says the status is unknown, and never that the period is open.
What UpBoard does not cover in finance#
Stated plainly, because a gap discovered alone costs more than a gap announced:
- closing with entries: UpBoard posts no adjusting entries and locks no period;
- fixed assets: no tracking and no depreciation schedule. Only one anomaly is detected, assets left unamortised;
- actual against budget: no budget comparison;
- expense reports: neither approval nor reimbursement.
If your Odoo holds data on any of these, UpBoard tells you so rather than letting you believe its figures include them.
The reference#
Accounting health score
Composite score 0-100 and grade A-F- No reads of its own: aggregates the outputs of O02 to O19.
- An axis with no usable data is not scored: the score is then reported as partial, naming the unscored axes. Never read a partial score as a complete one.
- An Odoo domain the customer does not have is excluded from the average rather than scored 100: zero anomalies for lack of a module is not everything-is-fine.
Available cash and runway
Cash balance, burn rate, runway- `account.move.line.balance` on liquidity accounts
- `account.account` to identify those accounts
- Undrawn credit lines, authorised overdrafts.
- Finding no cash entry does not yield a balance of 0 but an unknown balance: a false zero would read as bankruptcy.
- Runway is only reported when the burn rate is positive.
30, 60, 90 day cash forecast
Probability-weighted inflows and outflows- `account.move.amount_residual` and `invoice_date_due` (customers and suppliers)
- Opening balance: same accounts as O02
- Invoices not yet issued, orders not yet invoiced, payroll and tax deadlines that are not accounting entries.
- The collection rates are fixed assumptions, not a given customer's history: a habitually late payer is treated like everyone else.
- Recurring outflows not yet in the books (payroll, rent) are not projected, so the projected balance leans optimistic.
Cash alerts
Threshold crossings on balance and runway- No reads of its own: reads the outputs of O02 and O03.
- Unknown balance: no cash alert is raised at all, rather than a reassuring or alarming alert about a figure that is missing.
Bank reconciliation
Unreconciled bank lines and proposed matches- `account.bank.statement.line` not reconciled
- Open `account.move` (`amount_residual`) for matching
- Lists are capped (2,000 rows read): on a very large database the count shown is a floor, and the truncation is stated.
- A grouped payment covering several invoices is not matched.
Net cash position
Cash net of short-term debt, quick ratio- Cash: same accounts as O02
- `account.move.amount_residual` of open supplier invoices
- Financial debt beyond one year, provisions.
- The quick ratio is not reported when there is no short-term debt: a division by zero is not an excellent ratio.
Customer ageing balance
Receivables ageing by bucket- Open customer `account.move`: `amount_residual`, `invoice_date_due`
- `res.partner` for the customer name
- Draft invoices, unallocated credit notes.
- A commercial dispute in progress is not distinguished from a plain payment delay: both appear as an overdue receivable.
Customer credit risk
Default score 0-100 and exposure- Customer `account.move` and `account.payment` (history)
- `res.partner` (length of relationship)
- No external data (published solvency, declared default) enters the score: it only reads the history present in Odoo.
- A new customer with no history gets a score that says little.
Average collection period
DSO (days sales outstanding), countback method- Customer `account.move`: `invoice_date`, `invoice_date_due`, `amount_residual`
- `account.payment` for settlement dates
- An exceptional invoicing month distorts the following period's DSO, countback included.
- Per-customer DSO over 180 days is not comparable to the overall DSO over 365 days: do not put them side by side without saying so.
Customers to chase
Prioritised dunning queue, level and channel- Overdue customer `account.move`
- `mail.message` to know what has already been sent
- `res.partner` for the available channel
- A case chased less than seven days ago and still unanswered is not flagged again: useful against harassment, but the case temporarily disappears from the queue.
- A reply sent from an employee's mailbox rather than the customer's address may go unseen.
Payment promises
Tracking of customer commitments, kept and broken- `mail.message` on invoices (a commitment is an exchange, not a field)
- `account.move.amount_residual` and `invoice_date_due` (the expected payment)
- A commitment made over the phone and not recorded in Odoo is invisible: the indicator only measures what was written down.
Supplier ageing balance
Payables ageing by bucket- Open supplier `account.move`: `amount_residual`, `invoice_date_due`
- `res.partner` for the supplier
- Draft invoices, unallocated credit notes.
- An available early-payment discount is not flagged here.
Average payment period
DPO (days payable outstanding)- Supplier `account.move` and `account.move.line` (reconciliation)
- The sector benchmark shown is indicative and internal; it is not an official statistic.
- A partial payment spread over time yields an average that represents little.
Recommended payment date
Cash against supplier relationship trade-off- Open supplier `account.move`: `invoice_date_due`, `amount_residual`
- Payment terms negotiated outside Odoo, and early-payment discounts, are unknown to the indicator.
VAT forecast
VAT balance for the current period and tax code anomalies- `account.move.line` on VAT accounts: `balance`
- `res.company` for the filing periodicity
- `ir.module.module` to check the Belgian localisation
- Draft entries, hence VAT not yet posted.
- This is not a return: no carry-forward, no deduction pro rata, no special regime is applied.
- A balance computed mid-period is not the final balance.
Tax deadlines
90 day calendar (VAT, corporate tax, EC sales list)- Internal Belgian catalogue, dated by the code
- `res.company` for the fiscal year and the company registration number
- Belgian catalogue only, and maintained by hand: a legal date that changes requires a code change.
- Without a readable registration number on the company, company-level deadlines are shown by default rather than hidden.
- This calendar does not replace the one kept by your tax agent.
Profit and loss for the month
Monthly P&L with expected recurring charges- `account.move.line.balance` and `account.account.code`
- Expected recurring charges are an estimate based on the previous three months, not an entry: they are shown separately and must not be copied into a return.
- A month in progress is not a closed month, however it is presented.
Comparison with the previous year
Fiscal year to date against the same period of the previous year- `account.move.line.balance` and `account.account.code`
- A change of scope between the two years (an acquisition, a change of chart of accounts) is not detected: the comparison is made on the accounts as they stand.
- If either end falls in a period that is not locked, the cumulative figure is reported as not final.
Margin by product and by customer
Estimated gross margin on confirmed orders- `sale.order.line` of confirmed orders (`state in sale, done`)
- `purchase.order.line` of the last six months (`state in purchase, done`)
- `product.product.standard_price`
- Unconfirmed quotations, cancelled orders.
- The cost is an estimated cost price, not an accounting cost of goods sold: shipping, supplier rebates and differently valued stock are not included. A product shown as sold at a loss here is a signal to check, not an accounting conclusion.
- A product never purchased in the last six months keeps its cost price from the product record, which may be out of date.
- Lines without a product (manually entered fees) are outside the margin.
Does a figure look wrong?#
Two cases, handled differently. If the figure does not match the definition above, it is a bug: write to info@upboard.ai naming the indicator. If the figure matches the definition but not your expectation, it is a convention to discuss, and that is a conversation we welcome: these choices are documented precisely so they can be challenged.