GuideMarch 13, 2026

Project Management in Odoo 19:
Tasks, Milestones, Burndown Charts & Collaboration

INTRODUCTION

Your Spreadsheet-Based Project Tracker Is Lying to You

We audit Odoo implementations for a living. In roughly half of them, the project module is installed but barely used. Teams track tasks in Google Sheets, milestones in someone's head, and time in a separate app that nobody reconciles with invoicing. The result: project managers can't answer "are we on budget?" without spending 30 minutes cross-referencing three tools.

Odoo 19's Project module has changed significantly from earlier versions. The Gantt view is no longer a paid Enterprise-only afterthought — it's a first-class planning tool with dependency arrows, critical path highlighting, and drag-to-reschedule. Burndown charts are built in. Customer portal collaboration means clients see progress without you exporting PDFs every Friday. And the Timesheet integration now flows directly into invoicing with zero manual reconciliation.

This guide walks through every step of setting up a production-grade project management workflow in Odoo 19 — from initial project configuration through task hierarchies, milestone tracking, burndown analysis, time tracking, and customer-facing collaboration portals. Every configuration is tested on live client deployments.

01

Setting Up an Odoo 19 Project with Stages, Tags, and Automation Rules

Before creating tasks, you need a properly structured project. The decisions you make here — stage definitions, tag taxonomy, and automation rules — determine whether the Kanban board is useful or just colorful noise.

Enable Required Features

Navigate to Project → Configuration → Settings and enable the features your workflow needs. Here's what each setting controls:

SettingWhat It EnablesWhen to Use
Sub-tasksHierarchical task breakdown (parent → child tasks)Always — even simple projects benefit from breaking "Build feature X" into smaller work items
MilestonesDate-anchored progress markers linked to tasksAny project with deliverable deadlines or phase gates
TimesheetsTime tracking on tasks, flows into HR & InvoicingService companies billing by the hour; internal teams tracking capacity
Customer RatingsSatisfaction surveys sent when tasks closeClient-facing service desks and consulting projects
Project UpdatesPeriodic status reports with burndown snapshotsProjects longer than 2 weeks with stakeholder reporting needs

Define Kanban Stages

Stages drive the Kanban board, automation triggers, and reporting. Keep them lean — every stage a task passes through adds process overhead. Here's the stage structure we use across most client implementations:

Python — Stage setup via XML data (project module)
<odoo>
  <data noupdate="1">
    <!-- Kanban stages for project tasks -->
    <record id="stage_backlog" model="project.task.type">
      <field name="name">Backlog</field>
      <field name="sequence">10</field>
      <field name="fold">True</field>
    </record>

    <record id="stage_todo" model="project.task.type">
      <field name="name">To Do</field>
      <field name="sequence">20</field>
    </record>

    <record id="stage_in_progress" model="project.task.type">
      <field name="name">In Progress</field>
      <field name="sequence">30</field>
    </record>

    <record id="stage_review" model="project.task.type">
      <field name="name">Review</field>
      <field name="sequence">40</field>
    </record>

    <record id="stage_done" model="project.task.type">
      <field name="name">Done</field>
      <field name="sequence">50</field>
      <field name="fold">True</field>
    </record>
  </data>
</odoo>

Automation Rules for Stage Transitions

Odoo 19's automation engine lets you trigger actions on stage changes. Go to Project → Configuration → Automation and create rules like these:

Python — Server action: auto-assign on "In Progress"
# Trigger: When task moves to "In Progress" stage
# Model: project.task
# Action: Execute Python Code

for record in records:
    if not record.user_ids:
        # Auto-assign to the user who moved it
        record.user_ids = [(4, env.uid)]
    # Set the start date if not already set
    if not record.date_start:
        record.date_start = fields.Datetime.now()
Stage Count Matters

We've seen projects with 12 Kanban stages. Tasks spend more time being moved between columns than being worked on. Five stages is the sweet spot for most teams: Backlog, To Do, In Progress, Review, Done. If you need more granularity, use tags or custom fields — not more stages. Stages are for workflow state; tags are for categorization.

02

Task Hierarchies in Odoo 19: Parent Tasks, Subtasks, and Dependencies

Odoo 19 supports a two-level task hierarchy: parent tasks and subtasks. This isn't a limitation — it's a deliberate design choice. Deeper nesting creates tasks that nobody can find and progress calculations that nobody understands. If you need more than two levels, you need separate projects, not deeper nesting.

Creating a Work Breakdown Structure

The pattern we recommend: parent tasks represent deliverables (things the customer sees), and subtasks represent work items (things the team does). Here's how to set this up programmatically for bulk creation:

Python — Creating a task hierarchy via ORM
# Create a parent task (deliverable)
parent_task = env['project.task'].create({
    'name': 'Website Redesign — Homepage',
    'project_id': project.id,
    'milestone_id': milestone_q2.id,
    'planned_hours': 40.0,
    'tag_ids': [(6, 0, [tag_frontend.id])],
})

# Create subtasks (work items)
subtasks = [
    ('Design mockup in Figma', 8.0),
    ('Implement hero section (OWL component)', 12.0),
    ('Integrate product carousel API', 10.0),
    ('Cross-browser testing & fixes', 6.0),
    ('Stakeholder review & revisions', 4.0),
]

for name, hours in subtasks:
    env['project.task'].create({
        'name': name,
        'parent_id': parent_task.id,
        'project_id': project.id,
        'planned_hours': hours,
        'user_ids': [(6, 0, [])],  # Unassigned initially
    })

Task Dependencies (Gantt View)

In the Gantt view, you can create finish-to-start dependencies between tasks by dragging a connector from one task's end to another's start. This is critical for realistic scheduling — "Cross-browser testing" can't start until "Implement hero section" is done.

Dependencies are stored in the depend_on_ids many2many field on project.task. You can also set them via code:

Python — Setting task dependencies
# Task B depends on Task A (B can't start until A finishes)
task_testing = env['project.task'].browse(testing_task_id)
task_implementation = env['project.task'].browse(impl_task_id)

task_testing.write({
    'depend_on_ids': [(4, task_implementation.id)],
})

# Query all blocked tasks (dependencies not yet done)
blocked = env['project.task'].search([
    ('depend_on_ids.stage_id.fold', '!=', True),
    ('project_id', '=', project.id),
])
Progress Calculation

Parent task progress in Odoo 19 is calculated from subtask completion: closed subtasks / total subtasks * 100. It does not weight by planned hours. A 2-hour subtask and a 20-hour subtask contribute equally to the percentage. If you need hour-weighted progress, you'll need a computed field override on project.task — we cover this in our customization guide.

03

Milestones and Gantt View: Visual Project Planning in Odoo 19

Milestones are date-anchored markers that represent project phase completions or deliverable deadlines. Unlike task due dates (which slip constantly), milestones are commitments — they're what you report to stakeholders and what burndown charts measure against.

Creating and Linking Milestones

Navigate to Project → [Your Project] → Milestones (tab in project settings). Each milestone has a name, deadline, and an auto-calculated status based on linked task completion:

Python — Milestone creation and task linking
# Create milestones for a phased rollout
milestones_data = [
    ('Phase 1: Core Setup', '2026-04-15'),
    ('Phase 2: Data Migration', '2026-05-01'),
    ('Phase 3: User Training', '2026-05-15'),
    ('Phase 4: Go-Live', '2026-06-01'),
]

milestones = {}
for name, deadline in milestones_data:
    milestones[name] = env['project.milestone'].create({
        'name': name,
        'project_id': project.id,
        'deadline': deadline,
    })

# Link tasks to milestones
# All tasks under a milestone contribute to its progress
migration_tasks = env['project.task'].search([
    ('project_id', '=', project.id),
    ('tag_ids.name', '=', 'Migration'),
])
migration_tasks.write({
    'milestone_id': milestones['Phase 2: Data Migration'].id,
})

The Gantt View

Switch to the Gantt view from the view switcher on the task list. Odoo 19's Gantt view supports:

  • Drag-to-reschedule — move task bars to change start/end dates
  • Dependency arrows — visual lines between dependent tasks
  • Group by assignee, milestone, or stage — see workload distribution at a glance
  • Critical path highlighting — tasks whose delay would delay the entire project appear in a distinct color
  • Milestone markers — diamond icons on the timeline showing deadline positions
XML — Custom Gantt view definition (for module developers)
<record id="view_task_gantt_custom" model="ir.ui.view">
  <field name="name">project.task.gantt.custom</field>
  <field name="model">project.task</field>
  <field name="arch" type="xml">
    <gantt string="Project Planning"
           date_start="date_start"
           date_stop="date_deadline"
           default_group_by="user_ids"
           dependency_field="depend_on_ids"
           color="stage_id"
           progress="progress"
           thumbnails="{{'user_ids': 'avatar_128'}}"
           precision="{{'day': 'hour:half', 'week': 'day:half'}}"
           sample="1">
      <field name="milestone_id"/>
      <field name="planned_hours"/>
      <field name="tag_ids"/>
    </gantt>
  </field>
</record>
Gantt View Requires Date Fields

The Gantt view only renders tasks that have both date_start and date_deadline set. Tasks without these dates are silently hidden from the Gantt view — no error, no warning. If your Gantt chart looks empty, check that your tasks have date ranges. We add a server action that auto-sets date_start = today and date_deadline = today + planned_hours / 8 for tasks moved to "In Progress" without dates.

04

Burndown Charts in Odoo 19: Tracking Velocity and Predicting Delivery Dates

Odoo 19 includes a built-in burndown chart accessible from Project → Reporting → Burndown Chart. It plots remaining work (task count or planned hours) over time against an ideal burndown line. The gap between the two tells you whether you're ahead, behind, or on track.

How to Read the Burndown Chart

PatternWhat It MeansAction
Actual line below idealTeam is ahead of scheduleConsider pulling work forward from the next milestone
Actual line above idealTeam is behind scheduleReduce scope, add resources, or renegotiate deadline
Flat line (plateau)No tasks being closed — team is blocked or context-switchingCheck for dependency bottlenecks or unresolved blockers
Line goes upScope creep — new tasks added faster than old ones closeFreeze scope or move new items to a separate milestone

Custom Burndown Query for Dashboards

If you need burndown data in a custom dashboard or BI tool, query it directly from the ORM. Here's how to build a burndown dataset:

Python — Burndown data extraction via ORM
from datetime import timedelta
from collections import OrderedDict

def get_burndown_data(project_id, start_date, end_date):
    """Generate burndown chart data for a project.

    Returns a dict of dates -> remaining planned hours.
    """
    Task = env['project.task']
    all_tasks = Task.search([
        ('project_id', '=', project_id),
        ('parent_id', '=', False),  # Only parent tasks
    ])

    total_hours = sum(all_tasks.mapped('planned_hours'))
    burndown = OrderedDict()
    current = start_date

    while current <= end_date:
        # Tasks closed on or before this date
        closed = Task.search_count([
            ('project_id', '=', project_id),
            ('parent_id', '=', False),
            ('stage_id.fold', '=', True),
            ('date_last_stage_update', '<=', current),
        ])
        closed_hours = sum(
            Task.search([
                ('project_id', '=', project_id),
                ('parent_id', '=', False),
                ('stage_id.fold', '=', True),
                ('date_last_stage_update', '<=', current),
            ]).mapped('planned_hours')
        )
        burndown[current.isoformat()] = {
            'remaining_hours': total_hours - closed_hours,
            'remaining_tasks': len(all_tasks) - closed,
            'ideal': total_hours * (
                1 - (current - start_date).days
                / (end_date - start_date).days
            ),
        }
        current += timedelta(days=1)

    return burndown
Burndown vs. Burnup

Odoo's built-in chart is a burndown (remaining work decreases over time). If scope creep is your main concern, a burnup chart is more revealing — it shows two lines: total scope and completed work. When the scope line keeps rising, the problem is visible immediately. You can build a burnup view by extending the reporting model with a computed field that tracks cumulative scope additions.

05

Time Tracking Integration: From Task Timer to Client Invoice in Odoo 19

The real power of Odoo's project management is the Timesheet → Invoice pipeline. When a developer logs 3 hours on a task, that entry flows through to the project's analytic account, appears on the client's invoice, and updates the project budget — all without manual data entry.

Enabling and Configuring Timesheets

Go to Project → Configuration → Settings → Time Management and enable Timesheets. Then configure each project's billing:

Billing TypeHow Hours Map to RevenueBest For
Non-billableHours tracked for internal reporting onlyInternal projects, R&D, overhead tracking
Prepaid (Fixed Price)Hours consume a prepaid service package sold via Sales OrderRetainer contracts, support packages with hour caps
Time & MaterialsEach timesheet entry creates an invoice line at the employee's rateConsulting, custom development, hourly billing

The Timer and Timesheet Entry Flow

Users can log time in three ways: the built-in timer on the task form (click start, work, click stop), manual timesheet entry on the task's Timesheets tab, or the Timesheet grid view (weekly timesheet with tasks as rows).

Python — Programmatic timesheet creation (e.g., from an external tool)
# Create a timesheet entry linked to a task
env['account.analytic.line'].create({
    'project_id': project.id,
    'task_id': task.id,
    'employee_id': employee.id,
    'date': fields.Date.today(),
    'name': 'Implemented search filter for customer portal',
    'unit_amount': 3.5,  # Hours
})

# Query budget consumption for a project
project = env['project.project'].browse(project_id)
total_planned = sum(project.task_ids.mapped('planned_hours'))
total_logged = sum(
    env['account.analytic.line'].search([
        ('project_id', '=', project.id),
    ]).mapped('unit_amount')
)
budget_pct = (total_logged / total_planned * 100) if total_planned else 0
# Example: "Budget consumed: 67.3% (134.5 / 200.0 hours)"

From Timesheet to Invoice

For Time & Materials projects linked to a Sales Order, invoicing is a three-click process:

  • Step 1: Sales Order exists with a "Service" product set to "Timesheets" invoicing policy
  • Step 2: Team logs hours on tasks linked to that SO
  • Step 3: Navigate to Sales Order → Create Invoice → "Logged timesheets" option generates invoice lines from uninvoiced timesheet entries
Timesheet Validation

Enable Timesheet Validation in settings to require manager approval before logged hours are invoiceable. Without this, a junior developer logging 40 hours on a task that should have taken 10 becomes a surprise on the client's invoice. The validation step adds one click per week for the manager but prevents billing disputes that take hours to resolve.

06

Customer Portal Collaboration: Giving Clients Visibility Without Giving Up Control

Odoo 19's customer portal lets you share project progress with clients without adding them as internal users (which would consume a paid seat). Clients log into the portal, see their project's tasks, post comments, upload files, and track milestone progress — all through a clean, read-mostly interface.

Portal Access Configuration

Enable portal access per project: open the project settings and toggle Customer Portal. Then configure what clients can see and do:

Python — Portal access control (custom module extending project)
from odoo import models, fields

class ProjectProject(models.Model):
    _inherit = 'project.project'

    # Control what portal users see
    portal_show_tasks = fields.Boolean(
        string='Show Tasks on Portal',
        default=True,
    )
    portal_show_timesheets = fields.Boolean(
        string='Show Timesheets on Portal',
        default=False,  # Usually keep internal
    )
    portal_show_burndown = fields.Boolean(
        string='Show Burndown on Portal',
        default=True,
    )
    portal_allow_task_creation = fields.Boolean(
        string='Allow Task Creation from Portal',
        default=False,  # Enable for helpdesk-style projects
    )

Invite Customers to the Portal

Navigate to the project, click Share, and invite the client via email. They receive a portal login link. Once logged in, they see:

  • Task list — all tasks assigned to their project, filterable by stage and milestone
  • Task detail — description, attachments, chatter (comment thread), and stage history
  • Milestone progress — a visual bar showing completion percentage for each milestone
  • File uploads — clients can attach specifications, assets, and feedback directly to tasks
XML — Portal template customization (showing milestones)
<template id="portal_my_project_milestones"
          inherit_id="project.portal_my_project">
  <xpath expr="//div[hasclass('o_portal_my_project')]" position="inside">
    <div class="milestone-overview mt-4">
      <h4>Project Milestones</h4>
      <t t-foreach="project.milestone_ids" t-as="ms">
        <div class="milestone-row d-flex align-items-center mb-2">
          <span class="milestone-name flex-grow-1">
            <t t-esc="ms.name"/>
          </span>
          <span class="milestone-deadline text-muted me-3">
            Due: <t t-esc="ms.deadline" t-options="{{'widget': 'date'}}"/>
          </span>
          <div class="progress" style="width: 200px; height: 8px;">
            <div class="progress-bar bg-success"
                 t-attf-style="width: #{{ms.progress}}%"/>
          </div>
          <span class="ms-2 small">
            <t t-esc="int(ms.progress)"/>%
          </span>
        </div>
      </t>
    </div>
  </xpath>
</template>
Internal Notes vs. Portal-Visible Comments

Odoo's chatter has two modes: Log note (internal only) and Send message (visible to portal users and sent via email). Train your team to use Log note for internal discussions ("this client is frustrating" should never appear on the portal) and Send message only for client-facing updates. There's no undo once a message is portal-visible.

07

4 Odoo Project Management Mistakes That Erode Team Productivity

1

Not Setting Planned Hours on Tasks

Without planned_hours, burndown charts show task count instead of effort remaining. Closing a 1-hour typo fix and a 40-hour integration both move the burndown by the same amount. Your chart says you're 50% done when you've only completed 2% of the actual work. Stakeholders see a healthy burndown; the team knows the real story is different.

Our Fix

Make planned_hours a required field via a custom module: override project.task and add planned_hours = fields.Float(required=True). Tasks can't be saved without an estimate. Estimates don't need to be accurate — they need to exist so the burndown has meaningful data.

2

Using One Project for Everything

We've seen companies with a single project containing 3,000 tasks spanning two years. The Kanban board takes 8 seconds to load. Filtering is useless because there are 47 tags that mean different things to different teams. Burndown charts are meaningless because the "project" is actually 15 unrelated workstreams sharing a board.

Our Fix

One project per client engagement or internal initiative. Projects in Odoo are cheap — there's no per-project cost, and cross-project reporting handles the "big picture" view. Use project tags to group related projects (e.g., "Q2 Initiatives") and the All Tasks view for cross-project search.

3

Ignoring the Analytic Account Link

Every Odoo project is linked to an analytic account. This is how timesheets, expenses, and vendor bills aggregate into a single project P&L. If you don't set up the analytic account correctly — or worse, if multiple projects share the same one — your project profitability reports are garbage. You'll see revenue from Project A mixed with costs from Project B and have no way to untangle them.

Our Fix

Odoo auto-creates an analytic account per project. Don't override this default. Ensure that Sales Orders, Purchase Orders, and Expense Reports linked to a project use the same analytic account. Verify in Accounting → Reporting → Analytic that each project's account shows the expected revenue and cost lines.

4

Milestones Without Deadlines

A milestone without a deadline is just a label. Odoo will happily let you create milestones with no date set. These milestones won't appear on the Gantt timeline, won't trigger any deadline warnings, and won't contribute to the burndown chart's ideal line. They exist in the system but provide zero planning value.

Our Fix

Same approach as planned hours: make deadline required on project.milestone via a small custom module. Additionally, add a scheduled action that emails project managers weekly about milestones due within 7 days — Odoo doesn't do this out of the box.

BUSINESS ROI

What Proper Project Management in Odoo Saves Your Business

The ROI of using Odoo's project module properly isn't hypothetical — it's the difference between knowing your project health in real-time and discovering problems at the invoice stage:

3 hrs/wkSaved on Status Reporting

Burndown charts and milestone progress replace the weekly "update the spreadsheet and email it to the client" ritual. The data is live; the portal is the report.

23%Fewer Budget Overruns

Real-time budget tracking (planned vs. logged hours) catches overruns at 70% consumption, not 130%. Early warnings mean early course corrections.

$0Extra Tool Licensing

Replaces Jira, Harvest, Monday.com, or Asana. Project management, time tracking, and client collaboration in one system — no integration middleware, no data sync issues.

The compounding benefit: when timesheets flow directly into invoicing, you eliminate the "unbilled hours" leak. In our experience, service companies using disconnected tools lose 8-15% of billable hours to manual reconciliation errors and forgotten entries. Odoo's integrated pipeline closes that gap to near zero.

SEO NOTES

Optimization Metadata

Meta Desc

Complete guide to Odoo 19 project management: task hierarchies, milestones, Gantt views, burndown charts, time tracking, and customer portal collaboration.

H2 Keywords

1. "Setting Up an Odoo 19 Project with Stages, Tags, and Automation Rules"
2. "Task Hierarchies in Odoo 19: Parent Tasks, Subtasks, and Dependencies"
3. "Burndown Charts in Odoo 19: Tracking Velocity and Predicting Delivery Dates"
4. "4 Odoo Project Management Mistakes That Erode Team Productivity"

Your Project Management Tool Should Be Where Your Data Already Lives

The strongest argument for managing projects inside Odoo isn't any single feature — it's the elimination of data silos. When your tasks, timesheets, invoices, and customer communications live in the same system, you stop spending time moving data between tools and start spending time managing the actual project.

If you're evaluating Odoo 19 for project management or migrating from standalone tools, we can help. We configure project structures, set up milestone-based reporting, integrate time tracking with invoicing, and build custom portal views for client collaboration. The setup takes days, not months — and the ROI starts on week one.

Book a Free Project Management Assessment