# Build a sales order management system around how your orders really move

Canonical: https://erpfly.com/modules/sales-order-management/
Last updated: September 11, 2026

Category: Sales & CRM

The stock Sales Order in ERPNext or Odoo is fine until an order sells under cost, ships in three drops or gets confirmed by someone who shouldn't. Describe those rules once and erpfly generates a sales order management system that enforces them, as a module you own.

### Features

- **Margin floors per customer group.** Contractors at 18%, showrooms at 25%, whatever you actually use. Lines below the floor are named in the error, so the rep knows which one to fix.
- **Holds that explain themselves.** A held order records who held it, which rule fired and who released it. Your sales manager sees a short queue, not a mystery status.
- **Promised dates on every line.** Partial deliveries are normal. Losing track of what was promised for the second drop isn't. Each line carries its own date and a late-lines report reads from it.
- **Order changes with a paper trail.** Quantity cuts and date changes after confirmation go through the platform's own update path, so stock reservations and billing stay consistent.
- **Clean handover to billing.** Delivered lines become invoice lines with the same prices and tax template. Accounts doesn't rekey a thing from the warehouse.

### What gets generated

**ERPNext / Frappe app:**
- Min Margin % custom field on Customer Group (fixture)
- Sales Order validate and before_submit hooks in hooks.py
- Margin Hold Log DocType
- Workflow for held Sales Orders
- Late Order Lines Script Report

**Odoo addon:**
- sale.order inheritance with a margin hold state
- Promised date field on sale.order.line
- Margin floor on crm.team, using margin_percent from sale_margin
- mail.activity approval request for the sales manager
- List and search views for held orders

### Where sales orders actually go wrong

Nobody loses money on the order that goes quote, confirm, ship, invoice. You lose it on the other ones. The rep who matched a competitor's price without checking cost. The order confirmed on Friday for stock that left on Thursday. The second delivery that everyone assumed someone else had promised.

ERPNext and Odoo both handle the mechanics of a sales order well. What they can't know is your house rules: which customers get which margin, who is allowed to override it, and what "confirmed" means in your business. Those rules usually live in a sales manager's head and a shared inbox. That works until the sales manager goes on holiday.

### What goes into the sales order management system we generate

You describe the rules. erpfly writes the code that enforces them, and it's the kind of code a senior developer would be comfortable reviewing.

On **ERPNext** you get a Frappe app. Custom fields such as a margin floor on Customer Group ship as fixtures, so they move with the app between test and live sites. Checks hook into Sales Order document events in `hooks.py`. Approvals use a standard Workflow, which means the Approve button appears where your team already looks. If you're weighing that against Server Scripts, our note on [ERPNext customization](https://erpfly.com/erpnext-customization/) covers when each one makes sense.

On **Odoo** it's an addon that uses `_inherit` on `sale.order` and `sale.order.line`, extends the existing views instead of replacing them, and reads margin from the `sale_margin` module rather than recalculating it. Access rules are declared in `ir.model.access.csv`, not assumed.

Rules, not screens, are the product. The screens stay mostly as they were.

### A worked example: tiles below cost

Picture a tile and stone distributor with two kinds of trade customer. Contractors buy in volume and get an 18% margin floor. Showrooms buy smaller quantities and have a 25% floor.

A rep enters an order for a contractor: 400 m² of porcelain at $21.50 per m², $8,600 in total. The warehouse valuation rate for that tile is $17.90. Margin on the line works out to 16.7%, so the order is marked for approval. When the rep tries to submit, they get a message naming row 2 and the margin, not a vague "validation failed".

The sales manager opens the held orders list, sees the note that this contractor has a job lined up for next month, and approves it. That approval is logged against the order. Next quarter, when someone asks why the contractor account is thin on margin, the answer is there with a name and a date.

The same order ships in two drops. 250 m² go this week and the rest in three weeks, when the next container clears. Each line carries its own promised date, so when the container slips by five days the late-lines report shows exactly which customers were promised what. Your coordinator can call the contractor before the contractor calls you.

The snippet on this page is the core of the margin rule. It's short on purpose. Short rules get read.

### Requests we'd talk you out of

Some sales order customisations cost more than they save, and we'd rather tell you early.

- **Rebuilding credit limits.** ERPNext has credit limits per customer and company, and Odoo can warn on a partner's credit limit. Configure those first. Only generate code for the parts they genuinely can't express.
- **Editing confirmed orders by writing to the database.** Both platforms have a proper way to change a submitted order. Going around it leaves reservations and invoices out of step, and you'll find out at month end.
- **Auto-confirming orders straight from email.** Parsing a PDF purchase order into a draft is reasonable. Confirming it without a human glance is how you ship 1,000 units instead of 100.

### Linking orders to the CRM, stock and invoices

A sales order sits in the middle of everything. Upstream, won deals from a [custom CRM module](https://erpfly.com/modules/crm/) should arrive with the agreed prices already on them. Downstream, promised dates only mean something if they're checked against [inventory and reservations](https://erpfly.com/modules/inventory-management/), and delivered lines should flow into [invoice management](https://erpfly.com/modules/invoice-management/) without anyone retyping quantities.

Wholesalers tend to feel this most, which is why our page on [ERP for distribution businesses](https://erpfly.com/industries/distribution/) goes further into split shipments and back orders.

### FAQ

### Do we lose the standard Sales Order screen?

No. The module adds fields, checks and a workflow to the Sales Order you already use in ERPNext, or to sale.order in Odoo. Your team keeps the same form, list view and print format, with a few extra fields where the rules need them.

### Our orders come in from Shopify and WooCommerce. Will the rules apply to those too?

They will, because the checks run on the document itself, not on the form. If a web order breaks a rule it lands in the hold queue instead of failing silently. We'll ask whether web orders should ever be held, since customers have usually already paid.

### What happens to our customisations when we upgrade?

They live in a separate Frappe app or Odoo addon and never touch core files. Minor version upgrades rarely notice them. For a major jump, run the included tests on a copy of your site first.

### Does this change how taxes are calculated?

No. Tax templates and fiscal positions stay standard. We deliberately keep margin and approval logic away from tax logic, because mixing them makes both harder to audit.

### We track open orders in a spreadsheet today. Can we bring them in?

Yes. We generate an import script that maps your columns to Sales Orders and flags rows with unknown customers or items before anything is created. Import open orders only. Closed history is better left as a report export.

### How long before we can use it?

A first version usually shows up within minutes of the prompt. Plan for a few days of entering real orders on a test site, finding the rule you forgot to mention and asking for the change.