A system can go live on schedule and still leave the finance team invoicing in a spreadsheet the following Monday. When that happens, the cause is usually upstream of the project: nobody walked through one complete, real transaction before the estimate was signed.

This post is a practical template for doing that walkthrough and turning it into something an estimate can rely on. It applies to ERP, CRM, Salesforce, Odoo or any business system that touches orders, invoices and payments.

Pick a messy transaction, not a clean one

Choose a recent sale that had at least one exception: a partial delivery, an off-list discount, a credit note, a split payment. Clean transactions confirm the demo. Messy ones show the scope.

Record every step in the same shape

Walk the transaction with the people who actually did it. For each step, capture the same fields. A plain YAML file works fine and is easy to diff later:

transaction: "Recent sale with partial delivery and a credit note" steps: - step: order done_by: sales rep recorded_in: customer email + spreadsheet exceptions: [special price] approved_by: sales manager outside_system: [approval via chat message] - step: credit_note done_by: accounting recorded_in: accounting system exceptions: [partial return] approved_by: finance outside_system: [email thread] - step: payment done_by: treasury recorded_in: bank + spreadsheet exceptions: [paid in installments] approved_by: null outside_system: [reconciliation spreadsheet] 

The values above are a format example, not a real case. The interesting fields are approved_by and outside_system. That is where unestimated work hides.

Classify every gap before anyone estimates hours

Each thing that doesn't map cleanly to the target system goes into one of four buckets:

Bucket Meaning How to estimate
standard The product does it out of the box Near zero
configuration Fields, picklists, validation rules, declarative automation, permissions Hours, low risk
development Custom code, UI components, integrations Separate estimate, real risk
business decision Nobody has defined the rule Not hours. A meeting with someone who has authority

The last bucket is the one that blows up projects. "Who approves a credit note above a certain amount?" is not a configuration task. If it gets mixed into the configuration estimate, it comes back later as a change request, a moved date, or a spreadsheet that survives go-live.

A quick way to make this visible is to count gaps per bucket per step:

from collections import Counter gaps = [ ("order", "business_decision", "no backup approver for off-list discounts"), ("order", "configuration", "discount approval threshold"), ("credit_note", "business_decision", "no written limit for credit notes"), ("payment", "development", "bank file import"), ] by_bucket = Counter(bucket for _, bucket, _ in gaps) print(by_bucket) # Any business_decision > 0 means the estimate is not ready to sign. 

Same caveat: the list is illustrative. The rule at the end is the point.

Treat every spreadsheet as a requirement

For each spreadsheet in outside_system, write down what it calculates, where its inputs come from and who makes decisions with it. Some become formula fields or reports. Others hide a business rule nobody wrote down. Either way, they belong in scope, not in a "phase two" nobody funds.

Who sits in the room

The people who do the work every day, plus someone with authority to settle disagreements between departments. Without the second one, business-decision gaps stay open and the consultants end up guessing.

Use the same trace after go-live

A few weeks after launch, rerun the walkthrough with the same transaction type. If outside_system is still full, the project is not done, whatever the timeline says. Going live is a date. Closing the month without people staying late to reconcile two sets of numbers is the result.

I wrote about the business side of this, in Spanish, on our blog: why an ERP implementation can go live on time and still fail.

Juan Manuel Garrido, founder of EGA Futura