Most ERP customisations start as a feature request and end as a maintenance liability. This one started as a question about data entry and ended as a reusable framework that now sits under sales orders, purchase orders, invoices, bills and credit notes alike. The difference was not the code. It was the half hour we spent before writing any.
A customer who had migrated from Tally to Odoo described their workflow. When they enter a transaction, they do not think in unit prices. They think in quantity and total amount, because that is what the supplier document in front of them shows. Tally let them enter those two numbers and worked out the rest.
“Can Odoo do that?”
It is the kind of request that looks like a few hours of work. A field, a compute method, done. That is exactly why it is worth slowing down.
The requirement was not the requirement
Written down literally, the request reads: add a Line Total field and calculate Unit Price from it. That is not a business requirement. That is an implementation suggestion wearing a requirement’s clothes, and building it as stated would have produced a field on one form for one customer.
The actual problem was different. The customer was not asking us to recreate Tally. They were asking us to stop making them do arithmetic that the software already has enough information to do. Once we framed it that way, the design space opened up considerably — and so did the scope of who else in the business had the same problem.
This distinction matters more in ERP work than almost anywhere else. Users describe solutions because solutions are concrete and problems are diffuse. The job of the implementation partner is to walk the request back to the problem before committing anything to code.
The hidden cost of a five-second frustration
One manual calculation is nothing. That is precisely why it survives for years.
Consider a purchase executive booking a supplier invoice. The document says 500 kg of steel rod, Rs. 82,500. Nobody reading that document thinks Rs. 165 per kilogram. They think 500 and 82,500. Yet a conventional ERP form requires the user to derive the unit price before it will accept the line — and to do it again, in reverse, when they check their work against the supplier total.
Put that at organisational scale. A mid-sized manufacturer processing 350 purchase invoices a day at an average of eight lines each is asking its team for 2,800 derived calculations every working day. Over a year, that runs into the hundreds of thousands. None of them create value. Every one of them is an opportunity for a rounding error that surfaces three weeks later as a reconciliation difference nobody can explain.
Worse, the errors are systematic rather than random. When a user rounds Rs. 165.0163 to Rs. 165, the line total no longer matches the supplier’s document — and the person who notices is the accounts payable clerk chasing a two-rupee variance across a hundred invoices.
Software should adapt to the way people think. People should not have to adapt to the software.
Every mature ERP has good ideas in it
One of the more limiting assumptions in enterprise software is that good ideas only arrive from inside your own ecosystem. They do not. Every mature ERP carries decades of accumulated judgement about how businesses actually work, and that judgement is worth reading.
SAP’s manufacturing workflows are exceptional. Oracle is hard to beat on financial control. Microsoft Dynamics integrates business process end to end. ERPNext introduced several elegant document-driven concepts. And Tally remains, by some distance, one of the fastest systems in the world for transactional data entry.
We work across several of these platforms, and the same pattern holds in all of them: the feature users genuinely love is rarely the one that impresses in a demo. It is the one that removes a step they repeat forty times a day. Our job is not to imitate those features. It is to understand why users value them, and then decide independently whether the same insight belongs in the system we are building.
Why we refused to copy Tally
At Arsenal IT Consultants we make a deliberate distinction between copying software and learning from it.
Copying means reproducing another product’s screens, field names and behaviour inside a system that was never designed for them. It always works in the demo and always hurts at the first upgrade. Learning means understanding why a feature earned its place in a product that millions of people use daily, then implementing that insight natively.
Tally solved a real usability problem, and the insight was worth taking seriously. Its implementation was not, because Odoo already has an opinionated pricing engine: pricelists, discount policy, tax computation, tax-inclusive and tax-exclusive modes, multi-currency, unit-of-measure conversion and a well-defined ORM contract for how line values are computed. Forcing another application’s model into that would have meant overriding computations Odoo owns — and inheriting a permanent upgrade tax.
So we asked a better question: how would Odoo have implemented this feature if it had been in the product from the start? That reframing shaped every decision that followed.
Architecture before code, not after
The most common misconception about AI-assisted development is that it starts with a prompt. It does not. It starts with the questions no model can answer for you, because they are business decisions rather than technical ones:
- Should this apply only to sales, or to purchasing and accounting as well?
- If the user changes the quantity after entering a total, should the total hold and the unit price move, or the reverse?
- Does the entered total include tax or exclude it, and who decides?
- Where does a line discount sit relative to the derived price?
- What should happen when a record is created through an import, an API call or an EDI integration, where no user is present to trigger an onchange?
- What happens on a credit note, where the line is being reversed rather than created?
- Should a derived price override a pricelist, or should the system warn?
None of those questions required AI. They required an argument between engineers with ERP scar tissue. Only once they were settled did the implementation become mechanical — and that is exactly the point at which AI assistance becomes extraordinarily effective.
The framework: SmartLineTotalMixin
By the time those questions were answered, it was obvious we were no longer building a field. We were building a pricing entry framework, and the natural expression of that in Odoo is an abstract mixin.
Odoo’s sale order lines, purchase order lines and account move lines are separate models with separate fields, separate workflows and separate accounting consequences. They share almost nothing structurally. What they do share is a shape: a quantity, a unit price, a discount, a tax set and a resulting subtotal. That shared shape is the seam an abstract model is designed for.
SmartLineTotalMixin implements the derivation logic once against that abstract shape. Each concrete model inherits it and declares how its own fields map onto the contract. The result is a single tested implementation of the hard part — the arithmetic, the tax handling, the rounding, the guard conditions — reused across every document type instead of copy-pasted into four.

The practical consequences are worth spelling out:
- One place to fix a bug. A rounding defect found on a vendor bill is fixed for sales orders in the same commit.
- New document types are cheap. Supporting an additional line model becomes a mapping declaration, not a new feature.
- Native computation is preserved. The mixin derives the unit price and then hands control back to Odoo’s standard computation chain. Odoo still calculates the subtotal, the taxes and the accounting entries exactly as it always did.
- Upgrades stay boring. Because the module extends rather than replaces the pricing engine, version migration is a review rather than a rewrite.
Bi-directional, and honest about which direction
The behaviour users actually experience is bi-directional. Enter quantity and unit price, and the line total follows — standard Odoo. Enter quantity and line total, and the unit price is derived. The user picks whichever pair they have in front of them, and the third value fills itself in.
The genuinely difficult design decision is what happens on the next edit. If a user has entered 500 kg and Rs. 82,500, then changes the quantity to 550, there are two defensible answers: hold the unit price at Rs. 165 and recalculate the total, or hold the total at Rs. 82,500 and recalculate the unit price. Both are correct in different businesses. A vendor bill being matched to a physical document usually wants the total to hold. A sales quotation being revised usually wants the price to hold.
We did not guess. We made it configurable behaviour, because a framework that imposes one answer on every company is a framework that will be forked within a year.
Bi-directional logic also carries a trap that is easy to walk into. Quantity updates the total, the total updates the unit price, the unit price updates the total again — and the write either never settles or lands on a number the user never typed. Guarding against that recursion, so that a single edit produces exactly one round of recalculation and then stops, is unglamorous work. It is also the difference between a module that survives production and one that only survives the demo.
Tax, discounts and the arithmetic that bites
Deriving a unit price from a total is trivial until tax enters, at which point it becomes the part of the module that earns its keep.
If the entered total is tax-exclusive, the derivation is a straightforward division adjusted for any line discount. If it is tax-inclusive — the norm in a great many Indian and Gulf transactions — the tax has to be stripped out before the unit price can be derived, using the same tax computation Odoo itself will apply on the way back. Getting this wrong by using a naive divisor produces a line that looks correct on screen and reconciles incorrectly in the general ledger, which is the worst class of bug in accounting software: silent, plausible and discovered at audit.
The same applies to discounts. A line discount has to be respected in the derivation, not applied afterwards, or the derived unit price will not reproduce the total the user typed. And rounding has to follow the company’s decimal precision configuration rather than Python’s defaults, because the number displayed to the user must be the number that hits the ledger.
The module handles all three, and it handles them by delegating to Odoo’s own tax and precision machinery rather than reimplementing it. That is the difference between an extension and a fork.
Working when nobody is looking
A great deal of ERP data never passes through a form. It arrives by CSV import, by XML-RPC and JSON-RPC calls, by integration with a customer’s procurement portal. Logic implemented purely in onchange handlers — the easy path — simply does not fire in those contexts, which is how a module ships, passes UAT and then quietly produces wrong data for six weeks.
Building the derivation into the compute and create/write layer rather than the UI layer means it holds regardless of how the record was born. A line created through an XML-RPC call gets the same treatment as a line typed by a user in Chennai. That is not a feature you can demo, which is exactly why it tends to be skipped.
Where AI actually helped
We used Claude Code as an engineering partner throughout the build, and the honest account of what it changed is more interesting than the marketing version.
It did not decide the architecture. It did not know that tax-inclusive entry was the hard case, or that this customer’s purchase team mattered more than their sales team, or that configurability was worth the extra complexity. Those judgements came from three decades of implementation experience and an hour of arguing.
What it did was compress the distance between a settled design and working, tested code — scaffolding the mixin, generating the edge-case test matrix across tax-inclusive and tax-exclusive permutations, and catching inconsistencies between the specification and the implementation while we were still in a position to care. Work that would conventionally have queued behind a functional specification, an estimate, an approval and a sprint boundary was in a test environment the same day.
The result was a production-ready module delivered in hours rather than weeks. But the reason it was hours is not that AI writes code quickly. It is that we knew precisely what we wanted before we asked for it. AI-assisted engineering multiplies the quality of your thinking — which means it multiplies bad thinking just as efficiently.
AI did not replace software engineering. It removed the delay between deciding and delivering.
What the customer got
The customer asked for a field. What they received was a module — aitc_smart_line_total_entry — that works across sales orders, purchase orders, customer invoices, vendor bills and credit notes; respects their tax and discount configuration; behaves correctly under import and API; is configurable to their preferred quantity behaviour; and does not compromise a single line of Odoo’s native accounting.
Their purchase team stopped doing arithmetic. Their reconciliation variances stopped appearing. And Arsenal IT Consultants gained a framework that the next customer with the same friction will receive in an afternoon.
That is what we mean when we say we build systems rather than features. The customisation would have been faster to write. The framework was faster to own.
Running Odoo and living with a workflow that fights your team?
Arsenal IT Consultants designs Odoo extensions that stay upgrade-safe, respect native accounting and are built around how your business actually works. Talk to us about your Odoo setup.