Support

Need help?

Most answers are already written down. For everything else, Oneop does not publish a response time — there is no SLA instrument behind one — so the fastest thing you can do is send a complete first message.

Search the help centreEmail support@oneop.ioOpen your workspace

Worth including

  • The email you log in with
  • The URL of the screen it happened on
  • What you expected, and what happened instead

Goes somewhere else

Projects

From timesheet to invoice: connecting service delivery to revenue

By The Oneop team8 min read

TL;DR

A billable hour becomes revenue only if it is captured, marked billable, priced at a rate that is resolved and then frozen, approved, and converted to an invoice exactly once. Most services businesses lose money at the approval step, where hours sit unbilled long enough that nobody is willing to invoice them.

A services business sells hours and invoices documents, and the distance between those two facts is where the money goes missing. The chain runs: someone does work, someone records it, someone decides it is billable, something prices it, someone approves it, and eventually a document goes to a client. Every link in that chain is a place where an hour that cost you salary stops being worth anything.

This is not a tooling complaint. It is a data-modelling one. The reason unbilled hours age out is almost always that the hour and the invoice live in different systems with no enforced relationship between them.

The six states a billable hour passes through

A billable hour is not a single fact. It is a record that moves through six states, and a services system is only as good as its handling of the transitions.

StateThe question it answersWhat breaks here
CapturedDid the hour get recorded at all?Logged from memory on Friday, attributed to the wrong project
ClassifiedIs this billable to the client?Everything marked billable by default, then argued about later
PricedAt what rate?The rate is looked up at invoice time, not at work time
ApprovedDoes a person stand behind this?The approval queue is somebody’s inbox
ConvertedIs it on an invoice?Converted twice, or never
PostedIs it in the books?The invoice exists in a billing tool and not in the ledger

Most firms have tooling for state one and state six, and improvise states two through five. Improvisation at state four is the expensive one.

Rate resolution is a specificity problem, not a lookup

The price of an hour is not one number. It is the answer to a resolution order, and the order has to be explicit or people will disagree about what was agreed.

A realistic hierarchy has at least three tiers, resolved most-specific-first: a rate negotiated for this specific project beats a rate attached to a role such as “senior engineer”, which in turn beats the workspace default. Some firms add a client-level tier between project and role. The tier count matters less than the rule that a more specific rate always wins and a less specific one never overrides it.

In Oneop’s project management module, that resolution runs project rate, then an optional role rate tier that a workspace switches on deliberately, then the tenant default. The answer is written onto the time entry at approval, and nothing downstream re-derives it.

That last clause is the whole point. If the invoice re-derives the price when it renders, then raising a rate in March changes what January’s work was worth, and your revenue figures quietly restate themselves. Freezing the resolved rate onto the entry is what makes a later rate change safe.

The failure case: forty hours that aged out

Here is the concrete version, and it is not exotic.

A four-person team delivers a discovery phase in March. Time is logged in a tracker. Billing runs monthly from a spreadsheet that the finance person assembles by exporting the tracker and filtering it. In March’s export, one engineer’s forty hours are attributed to the internal “Discovery” project rather than the client’s project, because both are called Discovery and the picker sorts alphabetically.

Nobody notices, because the spreadsheet shows a total and the total looks plausible. In June, a profitability review flags the client engagement as unusually cheap. The forty hours are found. Now someone has to decide whether to send a client a three-month-old invoice for work they were told was complete and settled.

They do not send it. Forty hours at a real rate leaves the business permanently, and the only trace is a spreadsheet nobody kept.

Notice what actually failed. Not the time tracker, which recorded the hours accurately. Not the invoice tool, which issued exactly what it was given. The failure is that no single system held the relationship between “hours delivered against this engagement” and “hours invoiced against this engagement”, so nothing could report the difference while the difference was still fixable.

Approval is a gate, not a formality

Approval exists for two reasons that get conflated. The first is quality control: a reviewer catches the twelve-hour day that was really four hours and a travel day. The second is legal and commercial: approval is the moment the business commits to a version of events it is willing to defend to the client.

Approval also has to be the moment pricing is fixed, because it is the last point where all the context is present. Afterwards, the entry should be immutable in the ways that matter. If approved time can still be edited freely, approval has not gated anything.

The failure mode to design against is the approval backlog, which is how unbilled work ages. A weekly approval cadence with a visible queue converts hours while the person who logged them still remembers the day. A monthly cadence with no queue produces June conversations about March.

Write-offs should be explicit, not silent

Some delivered hours will not be billed. A fixed-fee overrun, a goodwill gesture, an internal error absorbed rather than passed on. That is normal, and it is a decision.

The unhealthy version is the silent write-off: hours that are never converted because nobody looked, which produces the same financial outcome with none of the information. A written-off hour tells you something about scoping, about a client, or about an estimate. A forgotten hour tells you nothing and repeats next quarter.

Practically, that means unbilled time needs an age profile rather than a total. “We have 310 unbilled hours” is not actionable. “We have 40 unbilled hours older than sixty days, on two engagements” is a decision with a deadline on it.

What the conversion step has to guarantee

Turning approved hours into an invoice is a state change on the hours, not only the creation of a document. Two guarantees make it safe.

Exactly once. The conversion must select the approved entries, lock them, write the invoice lines, and mark the entries converted inside a single transaction. In Oneop, that conversion locks the entries with SELECT … FOR UPDATE and marks them converted in the same transaction, so a second concurrent attempt finds nothing left to convert and produces nothing rather than a duplicate invoice.

Posted, not exported. Sending the invoice should produce a real accounting entry, not a CSV for somebody to re-key. Oneop posts debit Accounts Receivable, credit Revenue, credit Tax through the same ledger service the rest of Finance uses, which refuses an entry whose debits and credits do not balance. If the double-entry part is unfamiliar, double-entry accounting for founders is the short version.

Two boundaries worth stating plainly. Oneop refuses to mix currencies onto one document rather than converting silently, and retainers are a tracking layer that draws down against invoices without ever posting to the general ledger, so a drawdown cannot be mistaken for revenue.

Where reporting fits, and where it does not

The number you want on a Monday morning is delivered-but-unbilled hours by engagement and by age. Building that from exports is the failure at the top of this article repeating itself in a spreadsheet, because an export of time and an export of invoices have no shared key that survives the round trip.

Be realistic about what your system reports on today. Oneop’s reporting catalogue includes a Projects dataset covering projects by status, owner, dates and budget, and reports read live rows rather than a nightly copy, so an invoice raised at 10:58 appears in an 11:00 report. Time entries are approved and converted inside the projects module rather than queried as a reporting dataset, which is worth knowing before you plan a dashboard around them.

Also worth knowing: time tracking is a Pro-plan capability, projects are available from Starter, and billing itself is a separate switch a workspace turns on deliberately, because approved time posting to a ledger has financial side effects. The pricing page has the ladder.

The part you should not automate

Automating the conversion is safe. Automating the approval is not. An approval that happens because a timer expired is not an approval, and the first time a client disputes a line, you will discover that nobody in the building can say who stood behind it. When not to automate covers the general shape of this rule: automate the mechanical step, keep the judgement step attached to a person and a timestamp.

Frequently asked questions

What is revenue leakage in a services business?

Revenue leakage is delivered work that never reaches an invoice. In services it is rarely theft or a pricing mistake. It is hours logged late, hours logged against the wrong project, hours nobody approved, and hours that aged past the point where anyone is willing to put them in front of the client. The work was done and paid for in salary; the revenue side simply never happened.

Should non-billable time be tracked at all?

Yes, and separately. Non-billable time is how you measure the real cost of an engagement: internal reviews, rework, scoping calls, account management. If your team only logs billable hours, every project looks profitable, because the expensive half of the effort is invisible. The distinction belongs on the entry, not in a second spreadsheet.

What is WIP in professional services?

Work in progress is delivered, unbilled effort: time that has been logged but has not yet been converted to an invoice. It is an operational number rather than an accounting one in most small firms. The number that matters is its age profile, because the probability of billing an hour falls sharply the longer it sits unconverted.

Why should a billing rate be frozen onto a time entry?

Because otherwise changing a rate restates history. If an invoice re-derives its price from the current rate table every time it is opened, raising your rate in March silently changes what January's work was worth, and your billed revenue no longer reconciles to anything. Resolving the rate once, at approval, and writing it onto the entry keeps past work at past prices.

What stops the same hours being invoiced twice?

A conversion that is atomic and marks its inputs as consumed. If the process that turns approved hours into invoice lines selects those entries, locks them, writes the lines, and flags the entries as converted inside one transaction, a second concurrent attempt finds nothing left to convert and produces nothing. Without the lock, two clicks two seconds apart can produce two invoices.

Written by

The Oneop team

Writing from the team that builds Oneop — an AI customer platform with web chat and email on top of a real CRM, help desk, ledger and warehouse.

Related reading

Start on the free plan and see the real thing

No credit card. Free plan, 2 seats.

Free forever · 2 seats · No credit card