1. Why Retail Replenishment Is a Good ERP Stress Test
ERP demos are beautiful. Real operations are not.
Replenishment is where the prettiness ends. It is one of the first processes that stops being forms-and-documents and becomes a decision engine — the system has to think, not just store. To do it for real, a platform has to juggle:
- sales as a time series, not a single number
- forecasts that bend for seasonality and promotions
- supplier reality: lead time, MOQ, pack size
- multiple warehouses
- documents that generate themselves
- a human who can still say “not that supplier this week”
- and an answer to “why did the system order that?”
That mix is what makes it a fair fight. Anyone can model an invoice. Replenishment forces both platforms to show how they cope with rules that keep changing.
This is not a marketing piece. It answers the one question a CTO asks before committing:
How much effort does it take to build a real retail process — and to keep changing it?
So we counted the lines of code. Both ways.
2. Business Specification (Practical, Not Imaginary)
Scenario
- 15 retail stores
- 1 central warehouse
- 4 main suppliers
- Daily POS sales
- Weekly promotions
- Mixed transfer / purchase strategy
Requirements
Every night (02:00 AM):
For each SKU per store:
- Calculate average daily sales (last 28 days)
- Adjust with a promotion factor (if active)
- Multiply by supplier lead time
- Subtract:
- Current stock
- Incoming purchase orders
- Incoming transfers
- Apply:
- MOQ (minimum order quantity)
- Pack-size rounding
- If there is a deficit:
- Prefer a transfer from the central warehouse (if available)
- Otherwise suggest a Purchase Order
- Create draft documents
- Log an explanation (why this quantity)
- Provide a UI screen:
- View suggestions
- Approve
- Reject
- Recalculate
Explainability example:
“Calculated need: 94 units (ADS 4.7 × 14 days – 32 stock – 40 incoming). Rounded to 96 due to pack size 12.”
3. ERPNext Implementation
ERPNext is built on the Frappe Framework (Python backend, JavaScript frontend).
It already supports:
- Stock
- Reorder levels
- Material Requests
- Purchase Orders
- Stock Transfers
But once you add promotions, multi-store rules, MOQ/pack rounding, “buy vs transfer” logic, and explainability, you quickly go beyond the built-in reorder functionality.
3.1 Architectural Approach
Typical implementation:
- Custom DocType: Replenishment Rule
- Scheduled Python job
- Custom server-side report
- Client-side UI actions
- Hooks for automation and wiring
3.2 Data Model Extension
{
"doctype": "Replenishment Rule",
"fields": [
{ "fieldname": "item", "fieldtype": "Link", "options": "Item" },
{ "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse" },
{ "fieldname": "lead_time_days", "fieldtype": "Int" },
{ "fieldname": "promotion_factor", "fieldtype": "Float" },
{ "fieldname": "moq", "fieldtype": "Int" },
{ "fieldname": "pack_size", "fieldtype": "Int" }
]
}
Typical footprint: ~120–180 LOC including metadata.
3.3 Scheduled Job (Core Logic)
# app/replenishment/scheduler.py
import frappe
def nightly_replenishment():
rules = frappe.get_all("Replenishment Rule", fields="*")
for rule in rules:
avg_sales = calculate_average_sales(rule.item, rule.warehouse)
adjusted_sales = avg_sales * (rule.promotion_factor or 1)
required_qty = adjusted_sales * rule.lead_time_days
current_stock = get_stock(rule.item, rule.warehouse)
incoming = get_incoming(rule.item, rule.warehouse)
deficit = required_qty - current_stock - incoming
if deficit > 0:
rounded_qty = round_to_pack(deficit, rule.pack_size, rule.moq)
create_suggestion(rule, rounded_qty)
Helper functions (stock queries, SQL joins, report generation, rounding, document creation, idempotency, logging) typically add another ~180–300 LOC.
3.4 Suggestion Report
SELECT
item,
warehouse,
suggested_qty,
explanation
FROM `tabReplenishment Suggestion`
WHERE status = 'Draft';
Typical footprint: ~70–120 LOC.
3.5 Client-Side Approve Action
frappe.ui.form.on("Replenishment Suggestion", {
approve(frm) {
frappe.call({
method: "app.replenishment.approve",
args: { name: frm.doc.name },
callback() {
frm.reload_doc();
}
});
}
});
Typical footprint: ~40–80 LOC.
3.6 ERPNext LOC Summary
| Component | LOC (approx.) |
|---|---|
| Python core logic | 250–400 |
| Reports | 70–120 |
| Client JS | 40–80 |
| Metadata JSON | 120–200 |
| Total (repo LOC) | 480–800 |
Logic-only LOC: ~320–600
4. MyCompany Implementation
MyCompany is built on lsFusion, a declarative business logic platform.
Instead of writing procedural jobs, you define:
- Data properties
- Calculated expressions
- Actions
- Forms
4.1 Data Definition
CLASS Item;
CLASS Warehouse;
CLASS Rule;
item = DATA Item (Rule);
warehouse = DATA Warehouse (Rule);
leadTime = DATA INTEGER (Rule);
promotionFactor = DATA NUMERIC[10,2] (Rule);
moq = DATA INTEGER (Rule);
packSize = DATA INTEGER (Rule);
4.2 Calculated Properties
avgSales(Item i, Warehouse w) =
SUM quantity(Sale s)
WHERE s.item = i
AND s.warehouse = w
AND s.date >= currentDate() - 28
/ 28;
requiredQty(Rule r) =
avgSales(item(r), warehouse(r))
* promotionFactor(r)
* leadTime(r);
deficit(Rule r) =
requiredQty(r)
- currentStock(item(r), warehouse(r))
- incoming(item(r), warehouse(r));
4.3 Rounding Logic
roundedQty(Rule r) =
MAX(
moq(r),
CEIL(deficit(r) / packSize(r)) * packSize(r)
)
IF deficit(r) > 0;
4.4 Action
generateSuggestions() {
FOR r IN Rule DO {
IF deficit(r) > 0 THEN
NEW Suggestion {
rule = r;
quantity = roundedQty(r);
explanation =
"ADS × LT – stock – incoming = " + deficit(r);
};
}
}
4.5 UI
FORM suggestions
OBJECTS s = Suggestion
PROPERTIES s.rule, s.quantity, s.explanation;
4.6 MyCompany LOC Summary
| Component | LOC (approx.) |
|---|---|
| Data definitions | ~25 |
| Business logic (properties) | ~60–90 |
| Rounding | ~10 |
| Actions | ~30–40 |
| UI | ~20–30 |
| Total | ~145–195 |
5. Side-by-Side Comparison
| Metric | ERPNext | MyCompany |
|---|---|---|
| Logic paradigm | Procedural (Python + glue) | Declarative (properties + actions) |
| Job scheduling | Explicit scheduled job + wiring | Action-oriented model (often less glue) |
| UI wiring | Frequently needs client-side scripting | Declarative forms |
| LOC (logic-only) | ~320–600 | ~100–150 |
| LOC (full repo) | ~480–800 | ~145–200 |
6. Architectural Implications
ERPNext extensions often spread logic across Python, JavaScript, and metadata. MyCompany tends to centralize business rules as a declarative model.
More lines of code usually mean:
- higher cognitive load
- more integration glue
- more surface area for regressions
This does not automatically make one platform “better”. It tells you what kind of change cost you are buying.
7. AI-assisted engineering economics
There is a newer angle, too. The amount and shape of your code now decides how well AI tools can help — assist, refactor, generate extensions. The sprawl that slows a human slows the model the same way.
- Context cost: more files and glue layers usually increase the context needed for correct AI assistance — and raise inference cost.
- Verification cost: fragmented procedural logic often requires more test scaffolding and runtime checks.
- Refactoring cost: more coupling points make safe automated refactors harder (for humans and AI).
- Knowledge capture cost: scattered logic increases documentation, prompting effort, and supervision time.
In other words, LOC is not only a proxy for human maintenance. It is increasingly a proxy for AI-assisted evolution cost.
8. When Each Platform Wins
Choose ERPNext if:
- You need a broad ERP quickly (accounting + procurement + stock) and you want a large ecosystem.
- Your replenishment rules are relatively standard and change slowly.
- You prefer Python and community familiarity over a modeling-first paradigm.
Choose MyCompany if:
- Your competitive advantage is in custom business rules and fast iterations.
- You expect frequent rule changes and want the system to stay explainable.
- You want ERP as a constructor: a model you evolve, not a product you patch.
ERPNext in India: the real cost picture
ERPNext is especially popular in India (Frappe is Bengaluru-based), so it is worth being concrete about cost there. ERPNext looks free — until customization. At contractor rates of roughly ₹500–800/hour, the ~300 lines of custom logic this replenishment scenario needs in ERPNext cost meaningfully more to build and maintain over two years than the ~100 lines in a modeling-first system.
India-specific rules add customization points, not fewer:
- GST compliance — tax logic, e-invoice (IRN) generation and reporting touch almost every transaction.
- Multi-warehouse inventory valuation under Indian accounting practice.
- Reorder logic — ERPNext's standard reorder level rarely matches real replenishment, so teams override it.
The takeaway is not "avoid ERPNext" — it is a strong fit for standard, slow-changing processes with a large community. Just budget for the cost of change, not only the license: the cheaper a platform is to adopt, the more its customization and maintenance hours decide the real two-year total cost of ownership.
9. Conclusion
Lines of code are not the whole story. But on a process this gnarly, they track cognitive load — and cognitive load is what you keep paying for, month after month, long after the license was free.
In this replenishment scenario:
- ERPNext typically requires ~2–4× more custom code.
- MyCompany often expresses the same rules in a smaller, more centralized logic surface.
Appendix A: Git diff simulation (how you would measure this honestly)
If you want the “how many lines” comparison to be defensible, measure it using a real repository diff and a LOC tool (for example cloc)
with transparent inclusion rules. Below is a simulated but structurally realistic example.
ERPNext (Frappe app) — simulated diff
$ git diff --stat
app/replenishment/hooks.py | 34 +++++++
app/replenishment/scheduler.py | 140 +++++++++++++++++++++
app/replenishment/api.py | 88 +++++++++++++
app/replenishment/utils/sales.py | 74 ++++++++++++
app/replenishment/utils/stock.py | 92 +++++++++++++++
app/replenishment/utils/rounding.py | 38 ++++++++
app/replenishment/doctype/replenishment_rule/replenishment_rule.json | 165 +++++++++++++++++++++++++
app/replenishment/doctype/replenishment_suggestion/replenishment_suggestion.json | 142 +++++++++++++++++++++++
app/replenishment/report/replenishment_suggestions/replenishment_suggestions.py | 76 +++++++++++++
app/replenishment/report/replenishment_suggestions/replenishment_suggestions.js | 58 ++++++++++
app/replenishment/public/js/replenishment_suggestion_form.js | 62 +++++++++++
12 files changed, 971 insertions(+)
Typical interpretation:
- Logic-only LOC = scheduler + utils + API + report Python + minimal JS (~320–600)
- Total repo LOC includes metadata JSON (~480–900+ depending on UI/report fixtures)
MyCompany (lsFusion module) — simulated diff
$ git diff --stat
modules/replenishment/Replenishment.lsf | 182 +++++++++++++++++++++++++++
modules/replenishment/ReplenishmentForms.lsf | 54 ++++++++
modules/replenishment/ReplenishmentDocs.lsf | 31 ++++
3 files changed, 267 insertions(+)
The discipline that makes LOC comparisons meaningful: same specification, same measurement method, transparent inclusion rules.
Quick feedback
A quick signal helps us prioritize follow-ups.
Related posts
If you care about the practical consequences of ERP architecture and reducing dependency on contractors — here are more materials on DevLab Blog: