rishabh954/erp-system Turns Django Into an Enterprise Control Plane

A modular monolith for accounting, HR, inventory, and CRM, built around tenant isolation, auditability, and locked-down business workflows.

9 min read • View on GitHub • More from rishabh954

A wide editorial scene of a locked ledger at the center of several business modules feeding into it through narrow chutes. The image explains that the system treats ERP as controlled coordination, not loose CRUD spread across unrelated screens.
The repo’s core idea is simple: every business action flows through a governed ledger, not around it.
Key Takeaways

The hidden contract: every record belongs to a company

Most ERP projects start with modules. This one starts with control. The important question is not whether it can show invoices, employees, or stock. It is whether every record stays inside the right company boundary, even when the system is busy, automated, and shared across teams.

That is why the CompanyScoped pattern matters so much. It is the quiet foundation of the repo: business objects inherit company ownership, and the system treats cross-tenant leakage as a bug, not a feature gap. Soft delete fits the same logic. In ERP, deletion is rarely simple. Preserving history is part of the product.

The architecture only makes sense when you see the same request pass through scope, rules, and transaction locks in a fixed order.

What this buys the project: a record is never just a row. It is a row plus ownership, lifecycle, and audit context. That is the difference between a demo and a system you would trust with payroll or stock counts.

Why the service layer matters more than Django views

The repo makes a strong architectural bet. Views stay thin. Business logic lives in services. Repositories handle access patterns. That is not fashionable ceremony. In ERP software, it is how you keep one rule from being rewritten ten different ways across forms, APIs, and tasks.

class BaseService:
    def __init__(self, repository):
        self.repository = repository

    def create(self, data, company):
        self.validate(data, company)
        self.log_activity("create", company)
        return self.repository.create_for_company(data, company)

class BaseRepository:
    def get_by_company(self, company, **filters):
        return self.model.objects.filter(company=company, **filters)

The pattern is plain, but the effect is not. Once validation, logging, and notification sit above the repository, every module gets the same enforcement model. Accounting does not invent its own rules. HR does not bypass them. Inventory does not sneak around them because someone needed a faster view.

LayerJobRisk if you skip it
ViewCollect input and return responsesRules spread everywhere
ServiceOwn business logic and side effectsInconsistent workflows
RepositoryRead and write scoped dataTenant leaks and duplicated query code
Database transactionProtect concurrency and integrityDuplicate records and race conditions

That table is the real story. The repo is trying to keep each layer honest.

The part most ERPs get wrong: concurrency

The most satisfying detail in the codebase is also the least glamorous: sequence generation is treated as a correctness problem. Invoice numbers, purchase orders, and similar identifiers cannot collide just because two users clicked at the same time. In business software, that is not a corner case. It is a production incident waiting to happen.

A close-up mechanical scene of hands turning a metal dial on a vault lock while a duplicate key fails to fit another slot. The image explains how row locking prevents duplicate sequence numbers during concurrent writes.
A single locked sequence row is the difference between clean numbering and a corrupted audit trail.

The nice part is that the repo does not solve this with hand-wavy uniqueness checks. It uses transaction boundaries and row locking. That is the right shape for the problem, because correctness has to live where the race condition lives.

A system that keeps working after the user logs out

A credible ERP does work in the background. This repo leans into that with scheduled tasks for depreciation, low-stock alerts, and audit-log cleanup. That matters because a business system is not only a place where people type things. It is also a machine that must keep accounting for time, inventory, and retention rules when nobody is watching.

@shared_task
def process_depreciation():
    # update fixed asset values on schedule
    ...

@shared_task
def low_stock_alerts():
    # notify when inventory crosses a threshold
    ...

@shared_task
def cleanup_audit_logs():
    # enforce retention policy without manual intervention
    ...

That background layer gives the project real operating depth. It is not just a CRUD app with a scheduler bolted on. It is trying to behave like a system of record that continues to reconcile itself after users leave for the day.

What the module map reveals about the product

The repository is organized like a business, not like a generic web app. You can see the separation between core infrastructure and domain modules such as accounting, HRMS, inventory, sales, and authentication. That modular monolith shape is a smart fit here. ERP domains share data and rules constantly, so splitting them into microservices too early would only add friction.

AspectThis repoTypical CRUD app
StructureCore platform plus domain appsOne app, one view layer, one pile of models
Data modelCompany-scoped and auditableOften globally shared by default
AutomationCelery tasks for real business jobsAd hoc scripts or none at all
UI strategyServer-rendered web app plus API parityUsually one channel only
Business logicCentralized in servicesLeaks into views and serializers

That shape also explains the product’s ambition. It is trying to be a reusable ERP foundation, not a single-purpose internal tool. The difference shows up in the boundaries.

Where it sits in the ERP landscape

Against the bigger open-source ERPs, this repo is not competing on breadth. It is competing on clarity of architecture. Odoo and ERPNext bring years of ecosystem gravity, mature modules, and distribution. IDURAR is closer in spirit as a modern web-stack ERP. Tryton is the clean Python comparison if you care most about modular design.

ProjectArchitecture styleStackTenant isolationAutomationEcosystem maturityBest fit
rishabh954/erp-systemModular monolith with service layerDjango, Python, Celery, PostgreSQLStrong by designPractical, task-drivenEarlyA Django ERP blueprint
ERPNextFull ERP platformFrappe, Python, MariaDBBuilt inBroadHighTeams wanting a mature open-source ERP
OdooLarge modular platformPython, PostgreSQL, custom frontendStrongVery broadVery highCompanies that want reach and polish
IDURARModern web ERPNode, React, MongoDBProject-specificUseful but narrowerGrowingJavaScript teams wanting a similar stack
TrytonModular ERP frameworkPython, PostgreSQLStrongSolidMaturePython shops that value lean modularity

The honest read is that this repo is closer to a structured Django blueprint than a full ecosystem rival. That is not a weakness. It is the reason the codebase is interesting to study.

The verdict

This is a serious architectural sketch of an ERP, not yet a battle-proven platform. Its best ideas are the boring ones done well: isolate companies, centralize rules, lock critical writes, and keep background jobs alive. That discipline is rare, and it is the main reason the project stands out.

If you are evaluating it as a product, you should be cautious. The public footprint is thin, the ecosystem signals are limited, and there is not yet evidence of broad adoption. If you are evaluating it as a Django ERP design study, though, it is sharp, coherent, and worth learning from.