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.

Retail replenishment process overview diagram

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:

  1. Calculate average daily sales (last 28 days)
  2. Adjust with a promotion factor (if active)
  3. Multiply by supplier lead time
  4. Subtract:
    • Current stock
    • Incoming purchase orders
    • Incoming transfers
  5. Apply:
    • MOQ (minimum order quantity)
    • Pack-size rounding
  6. If there is a deficit:
    • Prefer a transfer from the central warehouse (if available)
    • Otherwise suggest a Purchase Order
  7. Create draft documents
  8. Log an explanation (why this quantity)
  9. 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

ComponentLOC (approx.)
Python core logic250–400
Reports70–120
Client JS40–80
Metadata JSON120–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

ComponentLOC (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

MetricERPNextMyCompany
Logic paradigmProcedural (Python + glue)Declarative (properties + actions)
Job schedulingExplicit scheduled job + wiringAction-oriented model (often less glue)
UI wiringFrequently needs client-side scriptingDeclarative 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

Was this useful?

A quick signal helps us prioritize follow-ups.

Continue reading:

If you care about the practical consequences of ERP architecture and reducing dependency on contractors — here are more materials on DevLab Blog: