Running an HVAC business with Odoo

How do I track HVAC job costs in Odoo?

Workflow overview: How do I track HVAC job costs in Odoo?
Workflow overview. Instructional screenshots show the actual demo interface.

Track HVAC job costs in Odoo with an employee hourly cost, one project timesheet, a posted allocated supplier bill and the native Real Costs report.

Alex needs to see which costs actually belong to the installation job. This Odoo analytic-accounting example brings one technician timesheet and one purchased service into the existing project’s Real Costs report. We reconcile the $195 total to its source records and keep unfinished work, uninvoiced revenue and missing costs visible.

Alex Rivera and Demo Air HVAC are fictional training examples. Prices, dates, quantities and business situations in this guide are illustrative.

Before you begin

Tested in stock Odoo 20.0-20260926 Community as the demo owner administrator, with official modules and standard configuration. Complete How do I manage an HVAC installation project in Odoo?, How do I purchase subcontractor services for an Odoo job? and How do I record technician time on an Odoo job?. Reuse Demo Installation H47 — S00011, its existing Project Account, installation task and P00010 service purchase. H48 left its DEMO-H48-DRAFT-REVIEW supplier bill in Draft; this lesson deliberately advances that same bill to a simulated posting. The owner has existing HR, timesheet, Project and Invoicing access. Official analytic, project/account, timesheet and purchase integrations are already installed; no Enterprise profitability bundle or new permission is added. The $600 fixed-price installation remains open and uninvoiced. Use a training company and local mail sink. All time, review decisions, account selections and postings below are fictional; no real work, supplier document, customer approval or payment is asserted.

Step-by-step workflow

Step 1. Check the existing project and costing scope

Open Project → Projects → Demo Installation H47 — S00011, then its existing [H47-INSTALL] Demo Installation Service H47 task. Verify the customer, assigned Demo Technician H42, project and Sales Order Item S00011. Keep the existing tasks, deadline and statuses. This example costs only one synthetic time entry and the H48 purchased review service. The project has its existing Project Account and Timesheets enabled. The separate H60 paid expense and earlier mixed inventory purchases belong outside this selection; do not assign them here merely because they exist in the company.

Action 1: Keep the existing installation task open. The actual target is highlighted in gold.
Keep the existing installation task open.
Action 2: Check the existing installation project. The actual target is highlighted in gold.
Check the existing installation project.
Action 3: Verify the intended fixed-price sales item. The actual target is highlighted in gold.
Verify the intended fixed-price sales item.
Action 4: Check the customer and assigned technician. The actual target is highlighted in gold.
Check the customer and assigned technician.

Step 2. Save the employee’s hourly cost before recording new time

Open Employees → Demo Technician H42 → Settings. Under Application Settings, enter Hourly Cost $30. Click Save manually, wait for saving to finish, reload and reopen Settings to verify $30. This is a chosen training cost rate, separate from a customer billing rate or a verified wage/payroll calculation. Set an appropriate reviewed basis in your business. The older H44 and time-off entries retained their original zero costs after this save; changing the employee setting did not retroactively reprice them in this test.

Action 5: Open the technician employee Settings. The actual target is highlighted in gold.
Open the technician employee Settings.
Action 6: Set a fictional hourly cost of $30. The actual target is highlighted in gold.
Set a fictional hourly cost of $30.
Action 7: Save the employee hourly cost once. The actual target is highlighted in gold.
Save the employee hourly cost once.
Action 8: Reopened Hourly Cost remains $30. The actual target is highlighted in gold.
Reopened Hourly Cost remains $30.

Step 3. Enter one time row on the intended installation task

Return to the existing installation task and open Timesheets → Add a line once. As the owner, select the existing Demo Technician H42 employee. Verify October 16, 2026, the actual default training date. Enter TRAINING H67: synthetic installation planning-cost rehearsal; no real visit, installation, customer approval or completed work. Set Time Spent 1.5. Click the task heading to leave the last cell; it displays 1h 30m. Keep exactly one row and leave the task In Progress. In real use, record actual time and its actual date.

Action 9: Open this installation task’s Timesheets. The actual target is highlighted in gold.
Open this installation task’s Timesheets.
Action 10: Add one synthetic time row. The actual target is highlighted in gold.
Add one synthetic time row.
Action 11: Keep the actual default date: October 16. The actual target is highlighted in gold.
Keep the actual default date: October 16.
Action 12: Choose the existing technician employee. The actual target is highlighted in gold.
Choose the existing technician employee.
Action 13: Select Demo Technician H42. The actual target is highlighted in gold.
Select Demo Technician H42.
Action 14: Label the planning time as fictional. The actual target is highlighted in gold.
Label the planning time as fictional.
Action 15: Enter 1.5 hours, then leave the cell. The actual target is highlighted in gold.
Enter 1.5 hours, then leave the cell.
Action 16: Review the single 1h 30m row before Save. The actual target is highlighted in gold.
Review the single 1h 30m row before Save.

Step 4. Save and reopen the same time entry

Click Save manually once and wait before reloading. Reopen Timesheets and check the same one-row date, employee, description and 1h 30m duration. The native time entry belongs to this task, project, Project Account and sales item. At the saved $30 cost rate it creates $45 of analytic labor cost. It is an analytic time cost without a supplier journal item; this step does not post payroll. The owner entered this training row for the selected employee; a new technician-role costing test is not claimed.

Action 17: Save this one time entry once. The actual target is highlighted in gold.
Save this one time entry once.
Action 18: Reopened time remains one entry of 1h 30m. The actual target is highlighted in gold.
Reopened time remains one entry of 1h 30m.

Step 5. Inspect the project’s costs before posting the supplier bill

Click the task’s Project field, then its Internal link to open the project form. It shows Estimated Costs $30, Real Costs $45 and Sales $636 at this stage. Click Real Costs: the project’s Margins pivot shows Timesheets (Fixed Price) −$45. The $30 estimate is different from the $45 recorded time cost, and the Sales button includes the example’s tax. The H48 supplier bill is still Draft, so its $150 service cost has not yet joined this report.

Action 19: Use the task’s Project field. The actual target is highlighted in gold.
Use the task’s Project field.
Action 20: Open the existing project’s internal link. The actual target is highlighted in gold.
Open the existing project’s internal link.
Action 21: Estimated Costs remain a separate $30 estimate. The actual target is highlighted in gold.
Estimated Costs remain a separate $30 estimate.
Action 22: Real Costs currently contain $45 of time. The actual target is highlighted in gold.
Real Costs currently contain $45 of time.
Action 23: Sales $636 includes the example tax. The actual target is highlighted in gold.
Sales $636 includes the example tax.
Action 24: Open this project’s Real Costs action. The actual target is highlighted in gold.
Open this project’s Real Costs action.
Action 25: The labor source contributes negative $45. The actual target is highlighted in gold.
The labor source contributes negative $45.
Action 26: Check the report’s actual date filter. The actual target is highlighted in gold.
Check the report’s actual date filter.

Step 6. Reopen the allocated purchase and its existing draft bill

Return through the project breadcrumb. Choose More → 1 Purchase Orders to open P00010, then Other Info and check its existing Project: Demo Installation H47 — S00011. That native selection supplied the project allocation used by this purchase and bill; a typed order reference alone would not do so. Click 1 Vendor Bills and open the existing DEMO-H48-DRAFT-REVIEW bill. Review the supplier, copied service scope, 1 Unit × $150, $9 demo tax, $159 total, October 15 Bill Date and P00010 link. Use this bill rather than creating another one.

Action 27: Return through the project breadcrumb. The actual target is highlighted in gold.
Return through the project breadcrumb.
Action 28: Open More for the linked purchase. The actual target is highlighted in gold.
Open More for the linked purchase.
Action 29: Open the existing P00010 purchase. The actual target is highlighted in gold.
Open the existing P00010 purchase.
Action 30: Review the purchase’s Other Info. The actual target is highlighted in gold.
Review the purchase’s Other Info.
Action 31: Keep the native installation-project allocation. The actual target is highlighted in gold.
Keep the native installation-project allocation.
Action 32: Open this purchase’s existing vendor bill. The actual target is highlighted in gold.
Open this purchase’s existing vendor bill.
Action 33: Retain the original draft-review reference. The actual target is highlighted in gold.
Retain the original draft-review reference.
Action 34: Bill Date remains October 15. The actual target is highlighted in gold.
Bill Date remains October 15.
Action 35: Review one service Unit. The actual target is highlighted in gold.
Review one service Unit.
Action 36: Review the existing $150 service price. The actual target is highlighted in gold.
Review the existing $150 service price.
Action 38: Draft Amount Due is $159 including $9 tax. The actual target is highlighted in gold.
Draft Amount Due is $159 including $9 tax.

Step 7. Record the review decision and post only the selected bill

Review the bill’s Account and tax before posting. This fictional direct-service example retains Cost of Goods Sold; that is a selected training classification, not advice for every subcontractor invoice. Use Log note to label H67 FICTIONAL COST REVIEW, identify P00010 and the job, and state that the earlier draft hold is released only for this simulated accounting exercise. Keep the original bill reference. State no real supplier document, work, tax determination or payment. Click Log once, then Confirm once. For a real business, complete the actual documentation, duplicate, account, tax, allocation and authorization review first.

Action 37: Review the retained Cost of Goods Sold account. The actual target is highlighted in gold.
Review the retained Cost of Goods Sold account.
Action 39: Open an internal bill-review note. The actual target is highlighted in gold.
Open an internal bill-review note.
Action 40: Label the review and posting as fictional. The actual target is highlighted in gold.
Label the review and posting as fictional.
Action 41: Log this one internal review; no recipients. The actual target is highlighted in gold.
Log this one internal review; no recipients.
Action 42: Keep the saved fictional review with this bill. The actual target is highlighted in gold.
Keep the saved fictional review with this bill.
Action 43: Confirm this existing training bill once. The actual target is highlighted in gold.
Confirm this existing training bill once.

Step 8. Reopen the posted bill and distinguish cost from payment

Wait for the bill to become Posted, then reload the same record. Its number is BILL/2026/10/0003, with original reference DEMO-H48-DRAFT-REVIEW and Amount Due $159. The existing service line’s 100% Project Account allocation generates one −$150 analytic supplier-cost entry. The journal records $150 expense, $9 Tax Paid and $159 Accounts Payable; the tax has no job analytic entry in this example. The bill remains unpaid. Posting recognizes this selected cost without making a payment, creating another bill or invoicing the customer.

Action 44: The posted bill retains its original reference. The actual target is highlighted in gold.
The posted bill retains its original reference.
Action 45: Reopen the same posted bill number. The actual target is highlighted in gold.
Reopen the same posted bill number.
Action 46: Amount Due stays $159; no payment was made. The actual target is highlighted in gold.
Amount Due stays $159; no payment was made.

Step 9. Open Real Costs from the project and reconcile the total

Return to the project form and click Real Costs again. Its button now shows $195. The project’s Margins pivot shows Costs −$195, comprising Timesheets (Fixed Price) −$45 and Purchase Orders −$150. Review the visible Date: For the Last 365 Days filter against your review period; the supplier-cost entry is October 15, 2026, and the time entry is October 16, 2026. The report shows costs with negative signs. Reconcile $45 + $150 = $195. Neither the $9 bill tax nor the $159 payable total is an additional job-cost row here.

Action 47: Return through the task’s Project field. The actual target is highlighted in gold.
Return through the task’s Project field.
Action 48: Open the same project, preserving job scope. The actual target is highlighted in gold.
Open the same project, preserving job scope.
Action 49: Real Costs now reconcile to $195. The actual target is highlighted in gold.
Real Costs now reconcile to $195.
Action 50: Open this project’s Real Costs again. The actual target is highlighted in gold.
Open this project’s Real Costs again.
Action 51: Selected costs total negative $195. The actual target is highlighted in gold.
Selected costs total negative $195.
Action 52: Recorded time contributes negative $45. The actual target is highlighted in gold.
Recorded time contributes negative $45.
Action 53: The purchased service contributes negative $150. The actual target is highlighted in gold.
The purchased service contributes negative $150.
Action 54: Keep the review period and project context visible. The actual target is highlighted in gold.
Keep the review period and project context visible.

Step 10. Inspect the two source rows without adding manual duplicates

Click List View and verify exactly two rows dated October 15 and 16 under the same Project Account, totaling −$195. Open the purchased-service row: its analytic item shows −$150, Cost of Goods Sold and a Journal Item. Click that field and its Internal link; it reopens the same posted supplier bill. Return through the project’s Real Costs action to inspect the labor row; in this configuration clicking that row opens its linked sales item. Do not use New to copy either cost into another manual analytic entry. Open the report from the project each time: directly reopening its generic URL showed other projects during our check.

Action 55: Inspect the two actual source rows. The actual target is highlighted in gold.
Inspect the two actual source rows.
Action 56: Review October 16 time beside October 15 service. The actual target is highlighted in gold.
Review October 16 time beside October 15 service.
Action 57: Open the existing purchased-service cost row. The actual target is highlighted in gold.
Open the existing purchased-service cost row.
Action 58: This one net supplier cost is negative $150. The actual target is highlighted in gold.
This one net supplier cost is negative $150.
Action 59: Inspect the source Journal Item link. The actual target is highlighted in gold.
Inspect the source Journal Item link.
Action 60: Follow its actual internal link to the bill. The actual target is highlighted in gold.
Follow its actual internal link to the bill.
Action 61: The source reopens the same posted supplier bill. The actual target is highlighted in gold.
The source reopens the same posted supplier bill.
Action 62: Return through this project’s Real Costs action. The actual target is highlighted in gold.
Return through this project’s Real Costs action.
Action 63: Return to the same two source rows. The actual target is highlighted in gold.
Return to the same two source rows.
Action 64: Open the labor row’s linked sales item. The actual target is highlighted in gold.
Open the labor row’s linked sales item.

Step 11. Check billing counters and keep the sales margin separate

From the labor row’s sales item, open Order Reference S00011. Ordered quantity remains 1 Unit at $600, Invoiced 0 and quantity to invoice 1, with no customer invoice. The native time linkage nevertheless sets Delivered 1.50 and creates an owner To-Do with an Upsell note. These are review signals, not proof of 1.5 completed installations or permission for an extra charge. Leave the decision pending. The sales order’s Margin $570 (95%) uses its $30 line-cost estimate; it is not the reconciled $195 project cost. Hours Recorded opens the single detailed 1h 30m entry; the badge rounds to 2.

Action 65: The fixed-price item is still one service Unit. The actual target is highlighted in gold.
The fixed-price item is still one service Unit.
Action 66: Open Order Reference S00011. The actual target is highlighted in gold.
Open Order Reference S00011.
Action 67: Ordered quantity remains one Unit at $600. The actual target is highlighted in gold.
Ordered quantity remains one Unit at $600.
Action 68: Delivered 1.50 is a time-derived review signal. The actual target is highlighted in gold.
Delivered 1.50 is a time-derived review signal.
Action 69: Sales Margin $570 is a separate estimate. The actual target is highlighted in gold.
Sales Margin $570 is a separate estimate.
Action 70: Keep the native Upsell To-Do pending. The actual target is highlighted in gold.
Keep the native Upsell To-Do pending.
Action 71: Open Hours Recorded; its badge rounds to two. The actual target is highlighted in gold.
Open Hours Recorded; its badge rounds to two.
Action 72: The detail is one actual entry of 1h 30m. The actual target is highlighted in gold.
The detail is one actual entry of 1h 30m.

Step 12. Record the remaining gaps and reopen the same result

Return to the project and use Log note once for H67 FICTIONAL JOB-COST REVIEW. Record the two cost sources, $195 selected total, unpaid bill, uninvoiced $600 service, separate estimate/margin and pending Upsell review. List missing actual revenue, remaining labor, material usage/valuation, other job expenses and overhead. Keep the separate paid expense and inventory purchases outside this selection to avoid double counting. Reload the project and open Real Costs through its button again: the same two costs remain. Reopening creates no extra entry, bill, invoice or payment. A complete profit conclusion remains pending those missing inputs.

Action 73: Record an internal job-cost review. The actual target is highlighted in gold.
Record an internal job-cost review.
Action 74: State the selected $195 costs and missing inputs. The actual target is highlighted in gold.
State the selected $195 costs and missing inputs.
Action 75: Log one internal review; no recipients. The actual target is highlighted in gold.
Log one internal review; no recipients.
Action 76: The saved review keeps unresolved inputs explicit. The actual target is highlighted in gold.
The saved review keeps unresolved inputs explicit.
Action 77: Reopen Real Costs through the project again. The actual target is highlighted in gold.
Reopen Real Costs through the project again.
Action 78: Reopen the same two cost records. The actual target is highlighted in gold.
Reopen the same two cost records.
Action 79: The same two cost rows still total negative $195. The actual target is highlighted in gold.
The same two cost rows still total negative $195.

Check the result

One saved synthetic October 16 timesheet contributes $45 at the employee’s $30 hourly cost. Posting the existing H48 bill contributes one $150 net analytic cost to the same Project Account, giving Real Costs $195 and an analytic balance of −$195. The native project pivot and two-row list reconcile to those records. The $159 supplier bill remains unpaid; its $9 illustrative tax is separate. S00011 remains one ordered $600 service Unit, uninvoiced, while its time-derived Delivered counter reads 1.50 and one native owner Upsell To-Do awaits review. Sales Margin $570 is a separate estimate, not actual job profit. Two internal review notes retain the scope and missing inputs without recipients or notifications. Reopening retains exactly these two cost rows. The preservation check kept all 13 other tasks, 21 other accounting entries, the other employee and all three earlier analytic entries exact in the original fields checked. Stock, payments, recurrence ends and pending renewal records also remained unchanged in the selected checks. The local mail sink receives no new message.

If your screen differs

Only the owner role and these selected synthetic costs were tested. This stock Community runtime exposes a project Real Costs/Margins report; its availability is demonstrated here, without a claim about every installation or the Enterprise financial-report bundle. The bill’s Analytic Distribution column was not exposed in this owner screen; native Project selection on P00010 supplies the allocation, and saved results plus the project/source drill-down verify the copied 100%. A generic report URL lost the job-specific action context and showed other accounts; return through the project’s Real Costs button and check date/account filters. Changing Hourly Cost left earlier zero-cost time untouched. A timesheet’s cost is separate from billing policy: this fixed-price Units line still has invoiceable quantity 1 despite Delivered 1.50 and its native Upsell reminder. Estimated Costs, the tax-inclusive Sales button and sales-line Margin are different measures. No complete profit, payroll posting, real acceptance, extra customer charge, expense reallocation, inventory-cost allocation, forced timesheet approval or historical cost recalculation is established.

Frequently asked questions

How do I track HVAC job costs in Odoo? Use the existing job project and Project Account, set a reviewed employee Hourly Cost before entering new time, and allocate reviewed supplier costs through the native project/purchase link. Here one 1.5-hour timesheet contributes $45 and posting the existing allocated service bill contributes $150, reconciled in the project’s Real Costs report to $195. The job remains open and uninvoiced, so this selected cost total is not complete profit.
Do Odoo timesheets automatically calculate labor cost? In this tested stock Odoo 20 Community setup, the new task timesheet uses the employee’s saved Hourly Cost: 1.5 hours at $30 produces −$45 in the Project Account. That is an analytic cost, not a payroll posting or customer billing rate. Setting $30 did not reprice the older zero-cost H44 and time-off entries in this test; review historical rows separately rather than assuming a retrospective update.
Why is Odoo project profitability missing some costs? Check the exact Project Account, date filter, source allocation, posted bill state and employee cost basis. The H48 draft contributed no posted analytic cost until Confirm; the project then showed its $150 net service cost alongside $45 time. Unallocated expenses, stock/material costs, remaining labor and overhead still need review. Open Real Costs from the project because a generic report URL can lose its job filter. The sales order’s estimated Margin is not this reconciled cost total.

Continue building the business

Continue with How do I review sales results in Odoo CRM?. Keep this project’s selected actual costs separate from sales forecasts, expected contract value and any eventual reviewed profit calculation.

Talk to an Odoo partner

Bring your current process and the result you need to Onward to plan your Odoo workflow. Talk to an Odoo partner.