A services firm bills $300/hr. They run 18 consultants. Each consultant logs 30 hours a week. That’s 540 hours, $162k a week, $8.4M a year. A 10% leak — forgotten timers, untagged time, wrong project — is $840k. Nobody finds it because the timesheet looks full.
This is the billable hours leak. Zoho Projects has the data. Nobody runs the audit. Here’s the audit.
Where the time goes missing
Five places. Ranked by how much money they eat.
- Untagged time entries — logged hours with no project or task. They’re real work. They don’t make it onto an invoice.
- Mis-tagged time — logged against an internal project when it should have been a client project. Or vice versa, which is worse.
- Round-down culture — “I worked from 9 to 12, that’s three hours” when it was actually 3.5. Bills don’t include the half hour.
- Forgotten timers — running for 14 hours overnight. Discovered next morning. Edited down to “1 hour”. 13 hours vanish.
- Non-billable that should be billable — kickoff calls, scoping sessions, change-request triage. Billable in the SOW. Logged as overhead by default.
The first three are visible in the data. The fourth and fifth need behavioral fixes plus discipline.
The Friday afternoon audit
Run this every Friday at 4 PM org time. Catch the leaks before timesheet lock on Monday.
// scheduled_friday_audit
// Reports anomalies to revenue ops channel for review before lock
import zoho.crm;
start_of_week = subDay(zoho.currentdate, 4); // last Monday-ish
end_of_week = zoho.currentdate;
entries = zoho.projects.getTimeLogs(start_of_week, end_of_week);
untagged = List();
suspicious_long = List();
weekend_entries = List();
zero_duration = List();
for each e in entries
{
hours = e.get("hours").toDecimal();
project = e.get("project_id");
task = e.get("task_id");
log_date = toDate(e.get("log_date"));
is_billable = e.get("billable");
user = e.get("user_email");
// Untagged
if(project == null || project == "" || task == null || task == "")
{
untagged.add({"user": user, "hours": hours, "date": log_date, "id": e.get("id")});
}
// Suspiciously long (likely forgotten timer)
if(hours > 10)
{
suspicious_long.add({"user": user, "hours": hours, "date": log_date, "id": e.get("id")});
}
// Weekend entries — flag for review, not auto-reject
day_of_week = log_date.getDayOfWeek();
if(day_of_week == 6 || day_of_week == 7)
{
weekend_entries.add({"user": user, "hours": hours, "date": log_date, "id": e.get("id")});
}
// Zero duration — probably a typo
if(hours == 0)
{
zero_duration.add({"user": user, "hours": hours, "date": log_date, "id": e.get("id")});
}
}
// Slack the summary
msg = "Weekly time audit — " + zoho.currentdate.toString("yyyy-MM-dd") + "\n";
msg = msg + "Untagged: " + untagged.size().toString() + " entries\n";
msg = msg + "Suspiciously long (>10h): " + suspicious_long.size().toString() + " entries\n";
msg = msg + "Weekend: " + weekend_entries.size().toString() + " entries\n";
msg = msg + "Zero hours: " + zero_duration.size().toString() + " entries\n";
zoho.cliq.postToChannel("revops-time-audit", {"text": msg});
Run it weekly. Pin the channel. The PM lead reviews Friday 5 PM. By Monday morning the timesheets are clean.
The billable-vs-internal leak
The classic mis-tag: a consultant logs an hour on “Internal Sync” when they were actually on a client kickoff. Project Manager doesn’t catch it. The hour never gets billed.
Fix: tag every project with a billable_default flag. New tasks under a billable project default to billable. New tasks under an internal project default to non-billable. Consultants can override but the default catches 90% of cases.
Then write a weekly report: time logged under a billable project but marked non-billable. Review every entry. If it should have been billable, fix and re-invoice (within billing cycle) or note for next cycle.
Forgotten timers — the structural fix
The 14-hour overnight timer happens because nothing reminds the consultant to stop. Two interventions:
- Daily 6 PM check-in: a Cliq message to anyone with a running timer that started before 5 PM. “Still working? Stop the timer if not.”
- Hard cap at 12 hours: a scheduled function stops any timer running over 12 hours and emails the user. They can re-enter manually with the correct hours. Better one annoyed consultant than 6 lost billable hours.
// scheduled hourly: stop timers older than 12h
running = zoho.projects.getRunningTimers();
for each t in running
{
started = toDateTime(t.get("start_time"));
duration_hrs = (zoho.currenttime - started).inHours;
if(duration_hrs > 12)
{
zoho.projects.stopTimer(t.get("id"));
sendmail
[
to: t.get("user_email")
from: "[email protected]"
subject: "Long-running timer stopped"
message: "Your timer for project " + t.get("project_name") +
" ran for " + duration_hrs.toString() + " hours and was auto-stopped. " +
"Please log the correct hours manually."
];
}
}
The reconciliation report
Monthly, run a reconciliation between Projects time logs and CRM deals.
- Total billable hours logged in Projects, by client
- Total invoiced via Books, by client
- Variance per client
A 5% variance is noise. A 15% variance is a leak. Anything above 25% is a system problem — wrong project codes, wrong client mappings, or a billing cycle skipped.
Project setup that prevents leaks
- One project per SOW. Don’t reuse projects across SOWs. Reconciliation becomes impossible.
- Hourly rate at the project level, overrideable per task only if SOW differs. Default to the master rate.
- Required fields on time log: project, task, billable, hours, note. No exceptions. Workflow rejects entries missing any of these.
- Task naming convention: “[Phase] [Activity]” — e.g., “Discovery - Stakeholder interviews”. Reports group cleanly.
Consultant-side discipline
Tools can’t fix culture. Three rules that move the number:
- Log time daily, not Friday. Friday-logged time is 60% remembered, 40% guessed.
- Use the timer for sessions longer than 30 minutes. Manual entry for shorter.
- Round to 6-minute increments (10ths of an hour). Anything finer is fake precision. Anything coarser leaks.
For the firm: a leak metric
Add a “billable utilization” board: total billable hours / total logged hours, by consultant, by week. Target above 75% for senior consultants, 65% for junior. Below that, you have a project mix problem, not a tracking problem.
Compare against “billable realization”: invoiced hours / billable hours. Should be near 100%. Gap means leaks.
For the broader services automation, see the Zoho CRM automation deep dive. For the bundle math when you have Projects, CRM, Books, and People, see Zoho CRM Plus bundle math.
Bottom line
Billable hours leak in five places: untagged time, mis-tagged time, round-down culture, forgotten timers, and “internal-by-default” tagging. Run a weekly audit. Auto-stop long timers. Reconcile against Books monthly. Default new tasks under billable projects to billable. The data is in Zoho Projects already. You just have to look. A 10-hour-a-week audit habit recovers six figures at a mid-sized firm.