Skip to content
Service · Project management module

An ERP project management module that watches the budget as well as the tasks

Task boards are easy to find. What's hard is knowing, on a Tuesday afternoon, whether a fixed-fee project is still making money. An ERP project management module can answer that because the hours, costs and invoices sit in the same database. erpfly extends Projects in ERPNext or Odoo to match how you quote, staff and bill work.

ERPNext v16

Works with ERPNext v15 and v16, and Odoo 17, 18 and 19.

Features

What your project management module can do

Budget hours next to actual hours

Each project carries the hours you quoted. Timesheets count against it as they're submitted, and the lead hears about it at 90%, not at invoice time.

Milestones that raise invoices

Completing a sign-off task drafts the milestone invoice at the agreed percentage. Finance reviews it rather than working it out.

No time on closed work

Logging hours to a completed or cancelled task is blocked with a clear message, so costs stop leaking into jobs that should be finished.

Change requests kept apart

Out-of-scope work goes on its own billable task at a separate rate, which makes the conversation with the client much less awkward.

Margin you can sort by

A report of revenue, cost and margin per project and per client, built from real timesheet costs, not an estimate typed in at kickoff.

The output

What actually gets generated

Real files in a real repository. Here’s the typical output when someone asks for project management.

ERPNext / Frappe app

  • Custom fields on Project (budget hours, billing model)
  • Project Milestone child table with billing percentage
  • Timesheet validate hook in doc_events
  • Sales Invoice draft on milestone Task completion
  • Project margin Script Report

Odoo addon

  • project.project inheritance with budget hours and billing model
  • project.milestone billing through sale.order.line
  • account.analytic.line constraint for closed tasks
  • Automated stage rules on project.task
  • Pivot and graph views of hours against budget
design_projects/projects/timesheet.py
import frappe
from frappe import _
from frappe.utils import flt

WARN_AT = 0.9

def check_time_logs(doc, method=None):
    """Timesheet.validate. budget_hours is a custom field on Project."""
    for log in doc.time_logs:
        if log.task:
            status = frappe.db.get_value("Task", log.task, "status")
            if status in ("Completed", "Cancelled"):
                frappe.throw(_("Row {0}: task {1} is {2}. Ask the project lead to reopen it.")
                             .format(log.idx, log.task, status))

    for project in {log.project for log in doc.time_logs if log.project}:
        budget = flt(frappe.db.get_value("Project", project, "budget_hours"))
        if not budget:
            continue
        submitted = frappe.db.sql("""
            select coalesce(sum(td.hours), 0)
            from `tabTimesheet Detail` td
            join `tabTimesheet` ts on ts.name = td.parent
            where td.project = %s and ts.docstatus = 1 and ts.name != %s
        """, (project, doc.name))[0][0]
        this_sheet = sum(flt(l.hours) for l in doc.time_logs if l.project == project)
        if flt(submitted) + this_sheet > budget * WARN_AT:
            frappe.msgprint(_("Project {0} is past 90% of its {1} budgeted hours.")
                            .format(project, budget), indicator="orange", alert=True)
Trimmed excerpt. The full module includes tests, fixtures and a README.

How it works

From a paragraph to a pull request

The long version
  1. 01

    Describe it

    In your own words. Paste the spreadsheet or a photo of the paper form if that's easier.

  2. 02

    Answer a couple of questions

    It asks only what it can't work out from your setup, like who's allowed to override.

  3. 03

    Try it on a sandbox

    A copy of your site with the module installed. Break it, then ask for changes.

  4. 04

    Merge when it's right

    Code lands as a pull request with tests. Your developer, or ours, reviews it first.

Why an ERP project management module and not another task app

Plenty of companies run projects in one tool, track time in another, and invoice from the ERP. It works until someone asks how much a project really cost. Then three exports get pasted into a spreadsheet and the answer arrives on Thursday.

An ERP project management module has one real advantage over a standalone tool: hourly costs, purchase invoices and sales invoices already live next to the tasks. ERPNext’s Project rolls up costing and billing from Timesheet and Activity Cost. Odoo’s project.project does similar through analytic lines, with hr_timesheet and sale_timesheet in Community.

So the stock modules aren’t the problem. The problem is that your billing model probably isn’t the stock one.

The parts people usually change

Most requests fall into a handful of buckets.

Budget control. The stock Project knows estimated cost, not quoted hours, and doesn’t warn anyone. We add budget hours and a threshold warning.

Billing model. Time and materials is well supported. Fixed fee with milestones, retainers with an hours cap, or a mix per project need extra logic.

Guardrails on timesheets. Blocking time on closed tasks, requiring an activity type, or capping a day at 12 hours so a typo doesn’t become 80.

Reporting. Margin per client and per project manager, which neither platform gives you out of the box in quite the shape you’ll want.

Templates. If every design job has the same 20 tasks in the same order, that’s a Project Template in ERPNext or a template project you duplicate in Odoo. We fill it with your real task names and default hours so new projects start with a budget already in place.

Where this logic lives matters as much as what it does. On ERPNext, erpfly puts it in a Frappe app with doc_events in hooks.py and custom fields shipped as fixtures, rather than a pile of Server Scripts that can’t be unit tested. On Odoo it’s an addon that inherits the stock models and extends their views, so upgrading Project doesn’t flatten your changes. Either way you get a README that says what was added, in words a new developer will follow.

A worked example with a fixed-fee design job

Take a 12-person engineering consultancy. They win a design project for $48,000, quoted at 400 hours. Billing is 30% at concept, 40% at detailed design sign-off, 30% at handover. Their blended internal cost is $42 an hour.

At concept sign-off, the lead marks the task complete. A draft invoice for $14,400 appears for finance to check.

Detailed design drags. By week nine the team has submitted 352 hours. An engineer’s Friday timesheet adds 14 more, taking the total to 366. That crosses 360, the 90% line, and the project lead gets an alert when the timesheet saves. Not a month-end surprise. There’s still time to talk to the client.

The client then asks for a second pump room layout that wasn’t in scope. That goes on a separate billable task at $110 an hour. It doesn’t eat the fixed-fee budget, and it shows up as its own line on the next invoice.

When detailed design is signed off, the $19,200 invoice drafts itself. The final $14,400 waits for handover.

Nothing exotic here. It’s the snippet on this page plus a milestone table and one more hook.

Requests we’d argue with

We like building things, but some of these projects go sideways.

Six-minute time increments across the whole company is one. Unless you’re a law firm billing that way, people will round anyway, and the data gets noisier, not better.

Rebuilding a full Gantt engine with 400 dependent tasks and automatic rescheduling is another. On Odoo Enterprise, use the Gantt that’s there. On ERPNext or Community, a simpler timeline plus sensible task dependencies usually does the job.

And if you don’t bill or cost by project at all, a task board is fine. Don’t pay for a module that manages money you aren’t tracking.

How projects connect to people and accounts

Timesheet cost rates are only as good as the employee data behind them, which is where HR and payroll come in. Milestone invoices flow into invoice management and on to your accounting ledger, with the project as a cost centre or analytic account.

Firms that bid on site work should also read our notes for construction companies. If you’re weighing what this kind of work costs to do by hand, ERPNext customization cost breaks it down.

Guides and terms for project management

Project management module questions

Something missing? Email [email protected] and a person will answer.

Can it replace Jira or Asana for our team?

For client project work that's billed and costed, often yes. For software teams running sprints with pull requests and CI hooks, we'd keep the dev tool and sync hours or milestones into the ERP instead. Rebuilding a mature issue tracker inside ERPNext isn't a good trade.

Does Odoo need Enterprise for this?

Project, timesheets and billing from timesheets are all in Community. Gantt views and resource planning are Enterprise features. We can build simpler planning views on Community, but we'll show you where they fall short before you commit.

Can staff log time from their phones?

Yes. Timesheets in both ERPNext and Odoo work in a mobile browser, and Odoo's mobile app covers them too. We usually add a stripped-down entry form so people log time daily instead of guessing on Friday.

How do we bring in projects from spreadsheets?

Send us the columns you have. We generate an import for projects, tasks and budgets, and you can load historic hours as submitted timesheets if you want margin reports to go back further.

What happens to the module when ERPNext or Odoo upgrades?

It lives in its own app or addon and doesn't touch core files, so upgrades don't wipe it. Task status values and timesheet fields do change between major versions now and then, which is exactly what the included tests are there to catch.

Your next module is one paragraph away

Write it the way you’d explain it to a new hire. We’ll turn it into an app you can read, test and install.

ERPNext v16

Create your account

Free to start. No card needed.

By signing up you agree to our terms and privacy policy.