You Delivered 200 Hours Last Month. You Billed for 140.
Every professional services firm has the same leak: hours worked but never invoiced. A developer logs time against a project but forgets to assign the right task. A consultant tracks hours in a spreadsheet that never syncs with accounting. A project manager approves timesheets on Friday but the invoice doesn't go out until someone manually creates it the following Wednesday. By then, three line items are missing because they were logged under the wrong project.
The gap between "hours delivered" and "hours billed" is pure revenue loss. For a 50-person consultancy billing at $150/hour, a 15% leakage rate means $2.7 million in annual unbilled work. The problem isn't that people forget to bill — it's that the path from timesheet entry to invoice line requires too many manual steps, each one an opportunity for data to fall through the cracks.
Odoo 19's Timesheet module closes this gap by creating a direct pipeline from time entry to invoice line. When configured correctly, an employee logs time on a project task, a manager approves the timesheet, and Odoo automatically generates the invoice with the correct billing rate, service description, and quantity. No spreadsheets, no copy-paste, no missing hours. This guide walks through the complete setup — from service product configuration through automated invoicing and profitability analysis.
Configuring Service Products for Timesheet Billing in Odoo 19
Everything starts with the service product. In Odoo 19, a service product with the invoicing policy set to "Based on Timesheets" tells the system: "don't invoice a fixed quantity — invoice whatever hours are logged against this sale order line." This is the foundation of time-and-materials billing.
Step 1: Create the Service Product
Navigate to Sales → Products → Products and create a new product. The critical fields:
Product Name: Senior Developer — Consulting
Product Type: Service
Invoicing Policy: Based on Timesheets
(Sales → General Information → Invoicing Policy)
Service Tracking: Task in Existing Project
── OR ──
Project & Task (creates both automatically)
Sales Price: $175.00 / hour
Unit of Measure: Hours
Cost: $85.00 / hour (internal cost for profitability)
# The "Service Tracking" field controls what happens when
# this product is added to a Sale Order:
#
# "Task in Existing Project"
# → Creates a task in a project you select on the SO line
# → Best for: ongoing retainers, T&M contracts
#
# "Project & Task"
# → Creates a NEW project + task per SO
# → Best for: one-off engagements, fixed-scope projects
#
# "Task in Sales Order's Project"
# → Groups all SO lines into one auto-created project
# → Best for: multi-service proposals (dev + QA + PM)Step 2: Set Up Multiple Service Tiers
Most firms bill different rates for different roles. Create one service product per billing tier:
| Service Product | Sale Price | Internal Cost | Margin | Service Tracking |
|---|---|---|---|---|
| Junior Developer | $100/hr | $45/hr | 55% | Task in Existing Project |
| Senior Developer | $175/hr | $85/hr | 51% | Task in Existing Project |
| Technical Architect | $225/hr | $120/hr | 47% | Task in Existing Project |
| Project Manager | $150/hr | $75/hr | 50% | Task in Existing Project |
The Cost field on the service product is what Odoo uses to calculate project profitability. Set it to the fully loaded cost of that role — salary, benefits, overhead. If you leave it at zero, the profitability report will show 100% margin on every project, which is useless for decision-making.
Setting Up Projects and Tasks for Timesheet Tracking in Odoo 19
With service products configured, the next step is connecting projects, tasks, and sale order lines so that every timesheet entry knows which invoice line it belongs to.
Step 1: Enable Timesheets on the Project
Navigate to Project → Configuration → Settings and ensure Timesheets is enabled. Then open your project and verify:
Project: Acme Corp — ERP Implementation
Allow Timesheets: [x] Enabled
(Project → Settings tab)
Timesheet Timer: [x] Enabled
(allows start/stop timer on tasks)
Bill from: Sale Order Item
→ Each timesheet line bills to the SO line
that created its task
# ── Key Relationship Chain ──
#
# Sale Order Line (product: "Senior Developer")
# └── creates Task ("Backend Development")
# └── Employee logs Timesheet (2.5 hours)
# └── Timesheet auto-links to SO Line
# └── Invoice pulls hours from SO Line
#
# This chain is AUTOMATIC when Service Tracking
# is set correctly on the product.Step 2: Create the Sale Order
Create a sale order for the client. Add one line per service tier. When you confirm the order, Odoo automatically creates the linked tasks:
# Sale Order: S00042
# Customer: Acme Corp
# ─────────────────────────────────────────────────
# Line | Product | Qty | Unit Price | Subtotal
# ─────────────────────────────────────────────────
# 1 | Senior Developer | 160 | $175.00 | $28,000.00
# 2 | Technical Architect | 40 | $225.00 | $9,000.00
# 3 | Project Manager | 80 | $150.00 | $12,000.00
# ─────────────────────────────────────────────────
# Total (estimated): $49,000.00
#
# NOTE: The "Qty" here is the ESTIMATED hours.
# With "Based on Timesheets" invoicing policy,
# Odoo invoices ACTUAL hours logged — not this estimate.
# The estimate appears on the quote for client approval.
#
# After confirming this SO:
# → Task "Senior Developer" created in Acme Corp project
# → Task "Technical Architect" created in Acme Corp project
# → Task "Project Manager" created in Acme Corp projectStep 3: Log Timesheets Against Tasks
Employees log time directly on tasks via Project → Tasks → [Task] → Timesheets tab, or through the dedicated Timesheets app which provides a weekly grid view.
# Timesheets → My Timesheets (Grid View)
# Employee: Jane Smith (Senior Developer)
# Week of March 9, 2026
# ──────────────────────────────────────────────────────
# Project / Task | Mon | Tue | Wed | Thu | Fri | Total
# ──────────────────────────────────────────────────────
# Acme / Backend Dev | 6.0 | 7.5 | 8.0 | 5.5 | 4.0 | 31.0
# Acme / Code Review | 1.0 | 0.5 | - | 1.5 | 1.0 | 4.0
# Internal / Team Meeting | 1.0 | - | - | 1.0 | - | 2.0
# ──────────────────────────────────────────────────────
# Weekly Total: | 37.0
#
# Only "Acme" tasks are billable — they link to SO lines.
# "Internal / Team Meeting" has no SO link → non-billable.
#
# The timer feature:
# → Click "Start" on a task to begin tracking
# → Click "Stop" to log the elapsed time
# → Odoo rounds to the nearest encoding UoM increment By default, the timesheet timer logs time to the exact minute. For billing purposes, most firms round to 15-minute increments. Configure this under Timesheets → Configuration → Settings → Minimum Duration. Set it to 15 minutes. Odoo will round up any entry shorter than 15 minutes to 0.25 hours. This prevents 2-minute entries from cluttering your invoices.
Configuring Employee Billing Rates and Rate Overrides in Odoo 19
Not every hour is billed at the same rate. Odoo 19 supports three layers of billing rate logic, each overriding the previous one. Understanding this hierarchy is critical for accurate invoicing.
| Priority | Rate Source | Where It's Set | Use Case |
|---|---|---|---|
| 1 (Lowest) | Service Product Price | Product form → Sales Price | Default rate for all employees on this service |
| 2 | Pricelist Rule | Sales → Pricelists | Client-specific negotiated rates |
| 3 (Highest) | Sale Order Line Override | SO line → Unit Price | One-off rate adjustments per engagement |
Client-Specific Rates via Pricelists
For clients with negotiated rates, create a pricelist that adjusts the billing rate per service product:
# Sales → Configuration → Pricelists → Create
#
# Pricelist: Acme Corp — Contract Rates
# Currency: USD
#
# ── Price Rules ──────────────────────────────────────
# Apply On | Computation | Value
# ─────────────────────────────────────────────────────
# Senior Developer | Discount | 10% → $157.50/hr
# Tech Architect | Fixed Price | $200 → $200.00/hr
# Project Manager | Discount | 5% → $142.50/hr
#
# Assign to customer:
# Contacts → Acme Corp → Sales & Purchases tab
# → Pricelist: "Acme Corp — Contract Rates"
#
# Now every SO for Acme Corp auto-applies these rates.
# The SO line shows the discounted price by default.
# Salespeople can still override on the line if needed.Employee Cost Rates for Profitability
The billing rate goes on the invoice. The cost rate is what Odoo uses to calculate margin. Set employee-specific costs under Timesheets → Configuration → Settings → Employee Costs:
# HR → Employees → Jane Smith → Settings tab
#
# Timesheet Cost: $90.00 / hour
#
# This overrides the product's "Cost" field for this
# specific employee. Useful when:
# → Senior devs on the same "Senior Developer" product
# have different salaries
# → Contractors vs. employees have different loaded costs
#
# Cost hierarchy (highest priority wins):
# 1. Employee-level Timesheet Cost
# 2. Product-level Cost field
# 3. Zero (if nothing is set — margin = 100%, which is wrong)
#
# ── Historical Cost Rates ──
# Odoo 19 supports date-based cost rates:
# From | Cost
# 2026-01-01 | $85.00/hr
# 2026-07-01 | $92.00/hr (after raise)
#
# Timesheets logged before July use $85;
# timesheets logged after use $92.
# Profitability reports reflect the correct cost per period.Timesheet Approval Workflows: Manager Review Before Invoicing
Invoicing unapproved timesheets is a recipe for client disputes. Odoo 19 provides a built-in approval workflow that gates timesheet entries before they become billable. Enable it under Timesheets → Configuration → Settings → Timesheet Approval.
# ── Enable Approval ──
# Timesheets → Configuration → Settings
# [x] Timesheet Approval
# Approval: By Manager
#
# ── Approval Workflow States ──
#
# Draft → Submitted → Approved → Invoiced
# | | | |
# Employee Employee Manager System
# logs time clicks reviews & (auto on
# "Submit" approves invoice
# creation)
#
# ── What Each State Means for Billing ──
#
# Draft: Editable by employee. NOT billable.
# Submitted: Locked for employee. Visible to manager.
# Approved: Locked for everyone. Ready for invoicing.
# Only approved timesheets appear in the
# "Create Invoice" wizard.
# Invoiced: Linked to an invoice line. Fully locked.
#
# ── Manager Approval View ──
# Timesheets → Managers → Timesheets to Approve
# → Filter by employee, project, date range
# → Bulk approve with "Approve" action
# → Reject with note (sends back to Draft) Set up a scheduled action (Settings → Technical → Automation → Scheduled Actions) that sends reminder emails to managers every Monday for any timesheets submitted but not yet approved from the previous week. Without this nudge, approval backlogs build up and invoicing gets delayed by weeks. The scheduled action filters account.analytic.line records where validated = False and date <= last_friday.
Locking Timesheets by Period
To prevent retroactive edits after invoicing, configure the timesheet lock date:
# Timesheets → Configuration → Settings
#
# Lock Timesheets: [x] Enabled
# Lock Date: End of previous month (auto-rolling)
#
# Effect:
# → Employees cannot add/edit timesheets before the lock date
# → Managers cannot approve timesheets before the lock date
# → Only users with "Timesheets Administrator" role can
# override the lock
#
# Example (today is March 13, 2026):
# Lock date = February 28, 2026
# → No one can log time for January or February
# → March timesheets are open for editing
#
# Best practice: Set the lock date AFTER invoicing the period.
# Workflow:
# 1. March 1: Lock February timesheets
# 2. March 3: Manager approves all February timesheets
# 3. March 5: Accountant creates February invoices
# 4. March 5: Lock date auto-advances to Feb 28Automating Invoice Creation from Approved Timesheets in Odoo 19
This is where the entire pipeline pays off. Approved timesheets are linked to sale order lines. Odoo calculates the billable quantity by summing approved hours per SO line, then generates invoice lines with the correct rate, description, and quantity.
Method 1: Manual Invoice Creation (Per Sale Order)
# Sales → Orders → S00042 (Acme Corp ERP Implementation)
# Click "Create Invoice" button
#
# ── Invoice Creation Wizard ──
#
# Create Invoice: [x] Timesheets (Approved)
# → Only invoices approved timesheet hours
#
# Invoice Period: March 1, 2026 – March 31, 2026
# → Filters timesheets by date range
#
# ── Preview: What will be invoiced ──
#
# SO Line | Ordered | Delivered | Invoiced | To Invoice
# ─────────────────────────────────────────────────────────────────
# Senior Developer | 160 hr | 62.5 hr | 0 hr | 62.5 hr
# Technical Architect | 40 hr | 12.0 hr | 0 hr | 12.0 hr
# Project Manager | 80 hr | 22.0 hr | 0 hr | 22.0 hr
#
# "Delivered" = sum of APPROVED timesheet hours for the period.
# "To Invoice" = Delivered − Already Invoiced.
#
# ── Resulting Invoice ──
#
# INV/2026/0089 — Acme Corp
# Line | Description | Qty | Price | Subtotal
# ────────────────────────────────────────────────────────
# 1 | Senior Developer | 62.50 | $175.00 | $10,937.50
# 2 | Technical Architect | 12.00 | $225.00 | $2,700.00
# 3 | Project Manager | 22.00 | $150.00 | $3,300.00
# ────────────────────────────────────────────────────────
# Total: $16,937.50Method 2: Batch Invoicing (All Clients at Once)
For firms with many active projects, creating invoices one-by-one is tedious. Use the batch invoicing flow:
# Sales → To Invoice → Orders to Invoice
#
# This view shows ALL sale orders with uninvoiced
# delivered quantities (including timesheet hours).
#
# Filter: "Invoicing Policy = Timesheets"
# Group by: Customer
#
# Select all → Action → Create Invoices
#
# ── Batch Wizard Options ──
#
# Create Invoice: [x] Timesheets (Approved)
# Invoicing Date: 2026-03-31
# Group by Partner: [x] (one invoice per customer,
# even if they have multiple SOs)
#
# Result:
# → 12 invoices created for 12 clients
# → Each invoice has one line per SO line with hours
# → All linked to the correct journal and payment terms
#
# Pro tip: Schedule this as a monthly cron job via
# Settings → Technical → Scheduled Actions:
#
# Model: sale.advance.payment.inv
# Method: create_invoices
# Interval: 1 month
# Execute: First business day of each monthMethod 3: Milestone + Timesheet Hybrid
Some contracts bill a fixed fee for milestones plus T&M for additional work. Odoo 19 handles this with mixed invoicing policies on the same sale order:
# Sale Order: S00043 — Acme Corp Phase 2
#
# Line | Product | Policy | Qty | Price
# ──────────────────────────────────────────────────────────
# 1 | Discovery Phase | Milestones | 1 | $8,000
# 2 | Development Phase | Milestones | 1 | $25,000
# 3 | Additional Dev Work | Timesheets | 40 | $175/hr
# 4 | Project Management | Timesheets | 20 | $150/hr
#
# Lines 1-2: Invoice when milestone is marked "Reached"
# on the linked project task.
# Lines 3-4: Invoice actual hours logged.
#
# This gives the client budget predictability on the core
# scope while allowing flexible billing for change requests.By default, the invoice line shows the product name and the total hours. Clients often want to see daily or task-level breakdowns. Enable this under Timesheets → Configuration → Settings → Invoice Details. Set it to "Group by Task" or "Group by Timesheet Entry" to append a detailed breakdown below each invoice line. This prevents "what did we pay for?" calls from clients.
Project Profitability Analysis: Tracking Margins in Real Time
Billing hours is only half the story. The real question is: are you making money on this project? Odoo 19's profitability analysis compares revenue (invoiced amounts) against costs (employee timesheet costs, expenses, and purchases) at the project level.
Accessing the Profitability Report
# Project → Acme Corp ERP Implementation → Profitability button
#
# ── Profitability Summary ──────────────────────────────
#
# Revenue
# Timesheet Invoiced: $16,937.50
# Milestone Invoiced: $0.00
# Other Revenue: $0.00
# Total Revenue: $16,937.50
#
# Costs
# Timesheet Costs: ($8,127.50)
# → Jane Smith: 62.5 hr x $90.00 = ($5,625.00)
# → Mark Lee: 12.0 hr x $110.00 = ($1,320.00)
# → Sarah Chen: 22.0 hr x $80.00 = ($1,760.00)
# → Unassigned: 0 hr = $0.00
# Expense Costs: $0.00
# Purchase Costs: $0.00
# Total Costs: ($8,127.50)
#
# ── Margin ─────────────────────────────────────────────
# Profit: $8,810.00
# Margin: 52.0%
#
# ── Budget Tracking ───────────────────────────────────
# Estimated Revenue: $49,000.00
# Invoiced to Date: $16,937.50 (34.6%)
# Remaining Budget: $32,062.50
# Hours Consumed: 96.5 / 280 (34.5%)
#
# The consumed percentage (34.5%) roughly matches the
# invoiced percentage (34.6%). Project is on track.Key Profitability Metrics to Monitor
| Metric | Formula | Healthy Range | Red Flag |
|---|---|---|---|
| Gross Margin | (Revenue - Timesheet Costs) / Revenue | 45%–65% | < 30% — underbilling or overstaffing |
| Budget Burn Rate | Hours Consumed / Estimated Hours | Should match % completion | > 80% consumed, < 50% complete |
| Billable Ratio | Billable Hours / Total Hours Logged | 75%–85% | < 60% — too much non-billable work |
| Realization Rate | Invoiced Hours / Available Hours | 70%–80% | < 55% — scope creep or poor tracking |
Analytic Accounts for Cross-Project Reporting
For multi-project profitability views, Odoo 19's analytic accounting lets you tag timesheets, expenses, and invoices with analytic plans. Navigate to Accounting → Configuration → Analytic Plans to set up dimensions like Department, Client, or Service Line. The resulting pivot reports let you answer questions like "What's the margin on all Senior Developer hours across all clients this quarter?"
4 Timesheet Billing Mistakes That Cost Consultancies Real Revenue
Timesheets Logged Against Tasks Not Linked to a Sale Order
An employee creates a new task manually (instead of letting the SO create it) and logs 20 hours against it. The timesheet exists, the hours are real, but there's no SO line to bill against. These hours never appear in the "Create Invoice" wizard. They're invisible to the billing team. We've seen firms discover $50,000+ in unbilled hours during audits because tasks were created outside the SO workflow.
Set a record rule that restricts task creation to managers only on billable projects. All billable tasks should be auto-created from SO lines. Add a scheduled action that reports weekly on any timesheet entries where so_line = False on billable projects — these are your revenue leaks.
Invoicing Before Timesheet Approval (Billing Disputed Hours)
Without the approval workflow enabled, all timesheet entries are immediately billable — including the 8-hour entry someone accidentally logged on the wrong project, or the 3 hours a junior developer spent on work that was already done. The invoice goes out, the client disputes it, and the credit note process begins. Each dispute costs 30+ minutes of back-and-forth and damages client trust.
Always enable timesheet approval. Always set the "Create Invoice" wizard to filter on approved timesheets only. Train managers to review timesheets weekly, not monthly. A Friday afternoon 10-minute review prevents a Tuesday morning 2-hour client dispute.
Zero-Cost Employees Inflate Profitability Reports
If you don't set the Timesheet Cost on employees or the Cost on service products, Odoo calculates project cost as $0. Your profitability report shows 100% margin on every project. Management makes staffing decisions based on fictional margins. The firm bills $500,000/year with a "100% margin" but actually operates at 15% because nobody accounted for the $425,000 in loaded labor costs.
Make timesheet cost setup part of the employee onboarding checklist. Use a server action that flags any timesheet entry where the associated employee has a cost of $0 — send an alert to HR to update the employee record. Quarterly, run a profitability report filtered by "cost = 0" to catch any gaps.
Forgetting to Handle Hours That Exceed the SO Estimate
You estimated 160 hours on the SO line, but the developer logged 185 hours. Odoo will happily invoice all 185 hours — it doesn't cap at the estimate. The client gets an invoice for 25 hours more than they agreed to. If you catch it before sending, you write off the overage. If the client catches it, you have an uncomfortable conversation about scope.
Enable SO line budget alerts. Odoo 19 shows a progress bar on the SO line comparing delivered vs. ordered quantity. Set up an automated action on sale.order.line that sends an email when delivered hours reach 80% of the estimate. This gives project managers time to request a change order or reallocate hours before overage occurs.
What Automated Timesheet Billing Saves Your Firm
The ROI of timesheet billing automation isn't theoretical — it's measurable in the first billing cycle:
Firms consistently find 10-20% of billable hours were going uninvoiced due to manual processes. Automated SO-to-invoice linking eliminates the most common leakage points.
No more cross-referencing spreadsheets with projects. Batch invoicing turns a full-day process into a 30-minute review-and-click workflow.
Manager-approved timesheets with task-level detail on invoices eliminate the "what were these hours for?" question that triggers 80% of billing disputes.
For a 30-person consultancy billing $3M annually, recovering 15% of leaked hours adds $450,000 to the top line — without hiring a single new person. The profitability dashboard then tells you which clients, services, and employees contribute most to margin, enabling data-driven decisions about where to invest and which engagements to renegotiate.
Optimization Metadata
Complete guide to Odoo 19 timesheet billing. Configure service products, set billing rates, enable approval workflows, automate invoice creation, and track project profitability.
1. "Configuring Service Products for Timesheet Billing in Odoo 19"
2. "Automating Invoice Creation from Approved Timesheets in Odoo 19"
3. "4 Timesheet Billing Mistakes That Cost Consultancies Real Revenue"