$ open blueprint
The high-level architecture explains the major domains inside BizNex OS.
The deeper architectural question is:
How do those domains maintain correctness when a single business operation affects inventory, accounting, sales, procurement, billing, and reporting at the same time?
That is the central problem an ERP architecture must solve.
A customer placing an order is not simply a database insert.
It may cause:
Sales Order
│
├── Inventory Reservation
│
├── Pricing Calculation
│
├── Tax Calculation
│
├── Invoice Creation
│
├── Payment Recording
│
├── Stock Movement
│
├── Accounting Journal
│
└── Audit Event
The architecture must ensure that these operations happen according to clearly defined invariants.
The goal is not merely:
"Everything succeeded."
The goal is:
The business state remains mathematically, financially, and operationally correct even when requests fail, workers crash, users retry operations, or external systems become unavailable.
Architecture Principles
BizNex OS is built around several architectural invariants.
1. Financial State Is Derived From Immutable Transactions
The system never treats a mutable balance as the ultimate source of truth.
Transaction
│
▼
Journal Entry
│
▼
Ledger
│
▼
Account Balance
│
▼
Financial Report
The balance is a projection of history.
Not the other way around.
2. Inventory Is a Movement System
Inventory is represented by movements.
Purchase Receipt +100
Sales Shipment -30
Customer Return +5
Adjustment -2
Transfer Out -10
Transfer In +10
──────────────────────────────
Available Stock 73
The current quantity is derived from movements and reservation state.
This makes historical reconstruction possible.
3. Every Domain Owns Its Invariants
Accounting owns:
Debits = Credits
Inventory owns:
Stock movements are valid
Warehouse quantities cannot violate rules
Sales owns:
Order lifecycle
Pricing
Invoice lifecycle
Procurement owns:
Purchase lifecycle
Approval rules
Supplier commitments
No domain should depend on another domain's internal implementation details.
4. Cross-Domain Operations Are Orchestrated
A sale may involve:
Sales
Inventory
Accounting
Payments
Audit
But the sales domain should not directly manipulate the internal tables of those domains.
Instead:
Sales Application Service
│
├── Inventory Service
├── Accounting Service
├── Payment Service
└── Audit Service
The orchestration layer coordinates the workflow.
5. Tenant Context Is Mandatory
Every tenant-owned operation carries:
organization_id
Tenant context is established before domain operations begin.
Request
│
▼
Identity
│
▼
Organization Context
│
▼
Domain Operation
The system never assumes that a client-provided organization ID proves authorization.
Complete System Architecture
Putting the major pieces together:
CLIENT APPLICATIONS
Web / POS / Mobile / API
│
▼
API Gateway
│
▼
Authentication / RBAC
│
▼
Tenant Context
│
▼
Application Services
│
┌──────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
Sales Domain Inventory Domain Procurement Domain
│ │ │
│ │ │
└──────────────┬───────┴───────────────┬────────┘
│ │
▼ ▼
Accounting Domain Billing / Payments
│ │
└───────────┬───────────┘
▼
Domain Event / Outbox
│
┌───────────┼────────────┐
▼ ▼ ▼
Celery Notifications Reporting
│
▼
External Systems
DATA LAYER
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
PostgreSQL Redis Object Storage
System of Record Cache Documents/Exports
The most important architectural boundary is between:
Domain State
and:
Derived / Asynchronous State
PostgreSQL contains authoritative business state.
Redis, reports, search indexes, dashboards, and notifications are derived or operational projections.
Domain Architecture
BizNex should be organized around business capabilities rather than database tables.
biznex/
│
├── identity/
│
├── organization/
│
├── catalog/
│
├── sales/
│
├── inventory/
│
├── procurement/
│
├── accounting/
│
├── billing/
│
├── payments/
│
├── reporting/
│
├── audit/
│
└── shared/
Each domain should contain its own:
Models
Services
Repositories
Policies
Validators
Domain Events
Selectors
The purpose is not to create arbitrary folder boundaries.
The purpose is to make ownership explicit.
Domain Ownership Matrix
| Domain | Owns | Does Not Own |
|---|---|---|
| Identity | Users, memberships, authentication | Financial state |
| Organization | Tenant configuration, branches | Transactions |
| Catalog | Products, SKUs, pricing definitions | Stock quantities |
| Sales | Orders, order lines, sales lifecycle | Ledger balances |
| Inventory | Movements, reservations, warehouses | Financial journals |
| Procurement | Purchase requests, POs, supplier lifecycle | Customer invoices |
| Accounting | Accounts, journals, ledgers | Physical stock |
| Billing | Invoices, payment state | Warehouse movements |
| Reporting | Read models and analytical projections | Authoritative transactional state |
| Audit | Security/business event history | Operational business state |
This table is more important than the folder structure.
It defines who is allowed to change what.
Aggregate Boundaries
Domain-driven design becomes especially important around aggregates.
For example:
SalesOrder
│
├── Customer
├── OrderLines
├── Pricing
└── OrderStatus
The SalesOrder aggregate controls its own invariants.
For example:
Order cannot be confirmed
if it contains no valid line items.
Inventory may have:
InventoryItem
│
├── SKU
├── Warehouse
├── Quantity
└── Reservation State
Accounting may have:
Journal
│
├── Journal Lines
├── Fiscal Period
└── Posting State
The key principle:
An aggregate protects its invariants; it does not become a dumping ground for unrelated business logic.
Accounting Architecture
Accounting is the financial backbone of the platform.
The fundamental structure is:
Business Transaction
│
▼
Accounting Event
│
▼
Journal Entry
│
▼
Journal Lines
│
▼
General Ledger
│
▼
Financial Statements
Chart of Accounts
Each organization owns its chart of accounts.
1000 Assets
│
├── 1100 Cash
├── 1200 Accounts Receivable
└── 1300 Inventory
2000 Liabilities
│
├── 2100 Accounts Payable
└── 2200 Tax Payable
3000 Equity
4000 Revenue
5000 Cost of Goods Sold
6000 Operating Expenses
The chart defines classification.
It does not contain the transaction history.
Transaction history belongs to journals and ledger entries.
Double-Entry Invariant
Every posted journal must satisfy:
SUM(Debits) = SUM(Credits)
For example:
Inventory Purchase
Debit
Inventory Asset $10,000
Credit
Accounts Payable $10,000
The journal is balanced.
For a sale:
Revenue Recognition
Debit
Accounts Receivable $15,000
Credit
Sales Revenue $15,000
And separately:
Cost Recognition
Debit
Cost of Goods Sold $9,000
Credit
Inventory Asset $9,000
A single business operation can therefore produce multiple accounting events.
Journal Lifecycle
A journal should have a controlled lifecycle.
Draft
│
▼
Validated
│
▼
Posted
│
▼
Locked
Once posted:
UPDATE journal
SET amount = ...
should not be the correction mechanism.
Instead:
Original Journal
│
▼
Reversing Journal
│
▼
Correct Journal
This preserves history.
Fiscal Period Control
Accounting periods should be explicit.
2026-01
2026-02
2026-03
...
A posted transaction should not silently modify a closed period.
Closed Period
│
▼
New Correction
│
▼
Adjustment Entry
This is critical for reliable financial reporting.
Inventory Architecture
Inventory is not simply:
Product.stock_quantity
It is a system of state transitions.
Purchase
│
▼
Receipt
│
▼
Stock Movement
│
▼
Available Inventory
Sales:
Sales Order
│
▼
Reservation
│
▼
Shipment
│
▼
Stock Movement
Returns:
Customer Return
│
▼
Inspection
│
├── Sellable ─────► Stock Increase
│
└── Damaged ──────► Separate Inventory State
Inventory State Model
A useful inventory model separates multiple concepts.
On Hand
│
├── Available
├── Reserved
├── Damaged
├── Quarantined
└── In Transit
Therefore:
Available
≠
On Hand
For example:
On Hand = 100
Reserved = 30
Available = 70
This is much more useful than one mutable stock_quantity field.
Inventory Movement Model
Every movement records:
movement_id
organization_id
product_id
warehouse_id
location_id
quantity
movement_type
reference_type
reference_id
occurred_at
created_by
Example:
Movement
--------------------------------
SKU = SKU-1029
Warehouse = WH-01
Quantity = -30
Type = SALE_SHIPMENT
Reference = SO-4021
This creates a complete stock history.
Inventory Reservations
Reservation is different from physical stock movement.
Order Created
│
▼
Reserve 30
│
▼
Available decreases
│
▼
Shipment
│
▼
On-Hand decreases
Therefore:
Reservation
≠
Stock Movement
This distinction prevents many ERP inventory bugs.
Concurrency in Inventory
Inventory is highly vulnerable to race conditions.
Imagine:
Available Stock = 1
Two customers purchase simultaneously.
Request A ──► Check stock = 1
Request B ──► Check stock = 1
Request A ──► Reserve
Request B ──► Reserve
Now the system has oversold.
The architecture must therefore make reservation atomic.
Conceptually:
BEGIN
Lock inventory state
Check available quantity
Create reservation
Commit
END
In PostgreSQL this can be implemented using appropriate row-level locking and transactional constraints.
The principle is:
The check and the state change must happen inside the same consistency boundary.
Sales Architecture
Sales should have an explicit lifecycle.
Draft
│
▼
Confirmed
│
▼
Reserved
│
▼
Fulfilled
│
▼
Invoiced
│
▼
Paid
│
▼
Completed
Not every business requires every state, but transitions should be explicit.
A random:
order.status = "paid"
should never bypass required domain rules.
Order Aggregate
A simplified model:
SalesOrder
│
├── Customer
│
├── Order Lines
│ ├── Product
│ ├── Quantity
│ ├── Unit Price
│ └── Tax
│
├── Pricing Snapshot
├── Tax Snapshot
└── Status
Historical orders should preserve the commercial facts that were agreed at the time.
If the product price changes tomorrow, yesterday's invoice should not change.
Pricing Architecture
Catalog pricing and transaction pricing are different concepts.
Product Catalog Price
│
▼
Pricing Engine
│
├── Customer Segment
├── Discount
├── Promotion
├── Quantity
└── Tax Rules
│
▼
Transaction Price
Once an order is confirmed, the relevant pricing information should be captured as a transaction snapshot.
This prevents historical transactions from changing when catalog configuration changes.
Procurement Architecture
Procurement follows its own lifecycle.
Purchase Request
│
▼
Approval
│
▼
Purchase Order
│
▼
Goods Receipt
│
▼
Supplier Invoice
│
▼
Three-Way Match
│
▼
Payment
Each state transition is explicit.
Three-Way Matching
The procurement system compares:
Purchase Order
│
├── Quantity
├── Price
└── Supplier
│
▼
Goods Receipt
│
├── Quantity Received
└── Date
│
▼
Supplier Invoice
│
├── Quantity
├── Price
└── Supplier
Only when the configured tolerance rules are satisfied should the invoice become payable.
For example:
PO Quantity = 100
Received Quantity = 100
Invoice Quantity = 100
PO Price = $10
Invoice Price = $10
Match succeeds.
If:
Invoice Quantity = 130
the system should require exception handling rather than silently approving payment.
Cross-Domain Transaction Architecture
This is the most important part of BizNex OS.
Consider:
Customer purchases 10 units for $1,000.
The business event touches:
Sales
Inventory
Accounting
Billing
Audit
The architecture should define the transaction boundary carefully.
Modular Monolith Transaction
Because BizNex begins as a modular monolith, tightly coupled operations can share one PostgreSQL transaction.
BEGIN TRANSACTION
Create Sales Order
│
▼
Reserve Inventory
│
▼
Create Invoice
│
▼
Create Accounting Journal
│
▼
Create Audit Event
│
▼
Create Outbox Events
COMMIT
If a critical operation fails:
ROLLBACK
The database returns to the previous consistent state.
This is one of the strongest reasons to start with a modular monolith.
Transaction Boundary
However, not every operation belongs inside one transaction.
Good candidates for the primary transaction:
Order State
Inventory Reservation
Invoice State
Journal Entry
Outbox Record
Poor candidates:
Send Email
Generate PDF
Call External Payment Gateway
Notify Slack
Send Webhook
Generate Large Report
Those should happen asynchronously.
Therefore:
Core State Change
│
▼
Database Transaction
│
├── Business State
└── Outbox Event
│
▼
Asynchronous Workers
Transactional Outbox
The outbox solves the dual-write problem.
Bad:
Save Order
│
▼
Publish Event
│
X
Broker unavailable
The order exists, but downstream systems never receive the event.
Better:
BEGIN
Save Order
Save Outbox Event
COMMIT
Then:
Outbox Worker
│
▼
Publish Event
│
▼
Mark Event Published
The database becomes the source of truth for the fact that the event needs to be published.
Domain Events
A sale can generate:
SalesOrderConfirmed
InventoryReserved
InvoiceCreated
JournalPosted
PaymentReceived
Events should describe facts.
Good:
InvoiceCreated
Avoid events that encode commands:
CreateInvoice
The distinction matters.
A command says:
"Please do this."
An event says:
"This already happened."
Event Flow
Sales Domain
│
▼
SalesOrderConfirmed
│
├────────► Inventory
│
├────────► Billing
│
├────────► Accounting
│
└────────► Reporting
Each consumer decides what that event means for its own domain.
Idempotency
Enterprise systems receive retries.
A payment provider may retry a webhook.
A worker may process the same event twice.
A client may submit the same request twice.
Therefore, important operations must be idempotent.
Example:
Payment Webhook
│
▼
event_id = evt_82921
│
▼
Already Processed?
│
┌──┴───┐
Yes No
│ │
Skip Process
Without idempotency:
Webhook Retry
│
▼
Payment Recorded Twice
│
▼
Financial Reconciliation Failure
Idempotency is therefore not an optimization.
It is part of correctness.
Distributed Evolution
Eventually, a domain may need to become an independent service.
The architecture can evolve:
TODAY
Modular Monolith
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Sales Inventory Accounting
LATER
Distributed Domains
Sales Service ──────► Event Bus
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Inventory Accounting Billing
But once a domain becomes a separate service:
Shared Database
X
should generally become:
Service
│
▼
Own Database
and cross-domain consistency must move from ACID database transactions toward asynchronous workflows and compensating actions.
Saga Architecture
Consider an order workflow after services become distributed.
Create Order
│
▼
Reserve Inventory
│
▼
Authorize Payment
│
▼
Create Invoice
│
▼
Complete Order
If payment fails:
Payment Failed
│
▼
Release Inventory
│
▼
Cancel Order
The system compensates for previously completed actions.
This is fundamentally different from a modular monolith transaction.
Modular Monolith
│
▼
ACID Transaction
versus:
Distributed System
│
▼
Saga + Events + Compensation
That distinction should be part of the long-term BizNex architecture.
Reporting Architecture
Reporting should not put heavy analytical queries directly against critical transactional tables whenever possible.
Transactional Database
│
▼
Domain Events
│
▼
Reporting Projection
│
▼
Dashboards
For example:
Sales Orders
│
▼
Sales Projection
│
├── Daily Revenue
├── Product Margin
├── Customer Sales
└── Sales Trends
The reporting model can be optimized for reads without compromising the transactional model.
Financial Reporting
Financial reports should ultimately derive from accounting data.
Examples:
Trial Balance
│
▼
General Ledger
Balance Sheet
│
▼
Account Classification
Income Statement
│
▼
Revenue - Expenses
Cash Flow
│
▼
Cash-related Transactions
The reporting layer should never independently calculate financial truth from random sales tables.
The accounting ledger remains authoritative.
Inventory Reporting
Inventory reporting can use projections:
Stock Movement Ledger
│
▼
Inventory Projection
│
├── Current Stock
├── Available Stock
├── Reserved Stock
├── Stock Valuation
└── Movement History
The projection can be rebuilt.
That is important.
If:
InventoryProjection
becomes corrupted, the system should be able to reconstruct it from authoritative movements.
Reconciliation Architecture
An ERP should continuously verify its own invariants.
Examples:
Accounting:
Total Debits = Total Credits
Inventory:
Opening + Movements = Closing
Procurement:
PO / Receipt / Invoice differences within tolerance
Payments:
Gateway totals = Internal payment totals
These checks should run automatically.
Scheduled Reconciliation
│
▼
Invariant Checks
│
┌───┴────┐
▼ ▼
Healthy Mismatch
│
▼
Alert
Reconciliation should not be a month-end activity only.
It should be a continuous control.
Audit Architecture
BizNex needs both operational history and security/business audit history.
Business Action
│
├────────► Domain History
│
└────────► Audit Event
Example:
{
"event": "purchase_order.approved",
"organization_id": "org_44",
"actor_id": "usr_1029",
"purchase_order_id": "po_8821",
"amount": "12500.00",
"timestamp": "2026-08-18T09:14:02Z"
}
For financial records, the accounting journal itself provides the authoritative financial history.
The audit layer provides the operational context:
Who approved it?
When?
From which session?
Under which organization?
Which workflow caused it?
Tenant Isolation Architecture
Tenant isolation must exist across every layer.
Tenant Isolation
│
┌──────────────────┼───────────────────┐
▼ ▼ ▼
API Layer Database Layer Cache Layer
│ │ │
▼ ▼ ▼
organization_id organization_id tenant-aware key
But it must also extend to:
Queues
Reports
Exports
Documents
Search
Object Storage
Audit Logs
Webhooks
Notifications
For example:
Celery Job
│
├── organization_id
├── actor_id
└── resource_id
A background worker must never process a tenant resource based solely on an unvalidated object ID.
Branch and Warehouse Context
Multi-tenant ERP systems often require another level:
Organization
│
├── Branch A
│ ├── Warehouse A1
│ └── Warehouse A2
│
├── Branch B
│ └── Warehouse B1
│
└── Branch C
Authorization can therefore become:
Organization Scope
│
▼
Branch Scope
│
▼
Warehouse Scope
│
▼
Resource Scope
A warehouse manager might have:
inventory.read
inventory.adjust
but only:
warehouse = WH-02
This is where RBAC and resource scoping meet.
Concurrency Architecture
ERP systems are concurrency-heavy.
Multiple users can simultaneously:
Sell the same SKU
Approve the same PO
Receive the same shipment
Process the same payment
Edit the same order
The architecture therefore needs explicit concurrency controls.
Possible mechanisms include:
Database Transactions
Row-Level Locks
Optimistic Versioning
Unique Constraints
Idempotency Keys
State Transition Guards
For example:
Purchase Order
Version = 7
User A reads version 7.
User B reads version 7.
User A updates:
7 → 8
User B attempts:
7 → 8
The system detects the stale version and rejects the update.
This prevents silent lost updates.
State Machines
Business workflows should be modeled as valid transitions.
For example:
DRAFT
│
▼
CONFIRMED
│
▼
FULFILLED
│
▼
INVOICED
│
▼
PAID
Invalid:
PAID → DRAFT
unless a specific reversal workflow exists.
State transitions should therefore be domain operations:
order.confirm()
order.cancel()
order.fulfill()
order.mark_paid()
rather than unrestricted field mutation:
order.status = "paid"
The domain should own the transition rules.
API Idempotency
For financial operations, clients should be able to provide an idempotency key.
POST /payments
Idempotency-Key: pay_82921
The server stores:
organization_id
idempotency_key
request_hash
response
created_at
If the same request arrives again:
Same Key
│
▼
Existing Result
│
▼
Return Original Response
This prevents duplicate financial operations caused by network retries.
External Payment Architecture
External payment systems should not be treated as authoritative for internal financial state without verification.
Client
│
▼
BizNex
│
▼
Payment Gateway
│
▼
Webhook
│
▼
Signature Verification
│
▼
Idempotency Check
│
▼
Payment State Update
│
▼
Accounting Journal
The webhook becomes the trusted asynchronous confirmation path after cryptographic verification.
Failure Handling
ERP architecture must explicitly define what happens when dependencies fail.
Redis unavailable
Redis should not become the source of financial truth.
Redis Down
│
▼
Database remains authoritative
Performance may degrade.
Financial correctness must not.
Celery unavailable
Core synchronous business transactions should continue whenever the operation does not require an external asynchronous dependency.
Outbox events remain stored:
PostgreSQL
│
▼
Outbox
│
X
Celery unavailable
When workers recover:
Outbox
│
▼
Worker
│
▼
Process Pending Events
External Payment Gateway unavailable
Do not mark the payment as successful merely because the request was sent.
Payment Request
│
▼
Gateway Timeout
│
▼
Payment = Pending
Later:
Webhook / Reconciliation
│
▼
Confirmed
│
▼
Accounting Entry
Data Integrity Layers
BizNex should enforce correctness at multiple levels.
Application Rules
│
▼
Domain Invariants
│
▼
Database Constraints
│
▼
Reconciliation
For example, accounting should not rely only on Python code to ensure balanced journals.
The system can additionally enforce structural constraints around journal lines and posting workflows.
The principle is:
Important business invariants should have more than one line of defense.
Database Constraints
Examples include:
Unique SKU per Organization
Unique Account Code per Organization
Unique Membership per User + Organization
Unique Idempotency Key per Organization
Foreign Key Integrity
Non-negative quantities where applicable
Valid status transitions
Database constraints protect against bugs from:
API
Admin scripts
Background jobs
Management commands
Future services
They provide a final integrity boundary.
Security Architecture
ERP systems contain high-value data:
Financial Records
Supplier Pricing
Customer Data
Employee Data
Inventory
Payments
Business Reports
Therefore security should be layered.
Identity
│
▼
Authentication
│
▼
Tenant Isolation
│
▼
RBAC
│
▼
Resource Scope
│
▼
Business Authorization
│
▼
Audit
No single permission check should be responsible for the entire security model.
Authorization Example
Consider:
Warehouse Manager attempts to adjust inventory.
The request becomes:
User
│
▼
Authenticated?
│
▼
Organization Membership?
│
▼
Role = Warehouse Manager?
│
▼
Permission = inventory.adjust?
│
▼
Warehouse Scope?
│
▼
SKU belongs to Organization?
│
▼
Adjustment Policy?
│
▼
ALLOW / DENY
If allowed:
Inventory Adjustment
│
▼
Stock Movement
│
▼
Accounting Impact
│
▼
Audit Event
One action can therefore affect multiple domains while remaining governed by clear ownership.
Observability Architecture
BizNex needs three forms of observability.
Logs
What happened?
Metrics
How often?
How quickly?
How many failures?
Traces
Where did the workflow spend time?
For a sale:
Request
│
├── Sales Service
│
├── Inventory Reservation
│
├── Accounting
│
├── Payment
│
└── Audit
Every workflow should carry:
request_id
organization_id
actor_id
correlation_id
This makes cross-domain troubleshooting possible.
Business Metrics
Observability should not stop at infrastructure.
ERP systems need business metrics too.
Examples:
Orders per minute
Revenue per hour
Inventory turnover
Stockout rate
Purchase approval time
Invoice aging
Payment failure rate
Unreconciled transactions
Journal imbalance attempts
This allows engineering teams to detect business anomalies, not just CPU spikes.
Reconciliation Engine
A mature ERP should have a dedicated reconciliation capability.
Reconciliation Engine
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
Accounting Inventory Payments
│ │ │
▼ ▼ ▼
Trial Balance Stock Ledger Gateway Match
Example:
Expected Inventory Value
│
▼
Accounting Inventory Asset
│
▼
Compare
│
┌───┴────┐
▼ ▼
Match Difference
│
▼
Alert
The system should make discrepancies visible instead of silently correcting them.
Reporting and Read Models
Transactional models are optimized for correctness.
Reporting models are optimized for queries.
Those are different concerns.
Transactional Model
│
▼
Domain Events
│
▼
Reporting Pipeline
│
▼
Read Models
│
┌──────────┼──────────┐
▼ ▼ ▼
Revenue Margin Inventory
This allows reporting workloads to grow without constantly competing with operational transactions.
Document Architecture
ERP systems generate:
Invoices
Purchase Orders
Receipts
Statements
Reports
Exports
Large documents should not be generated synchronously during critical transactions.
Instead:
Invoice Created
│
▼
Document Job
│
▼
Celery Worker
│
▼
PDF Generation
│
▼
Object Storage
│
▼
Signed / Authorized URL
The invoice's financial state remains independent of PDF generation.
File and Object Storage Isolation
Documents must carry tenant context.
tenant/
org_44/
invoices/
purchase-orders/
receipts/
reports/
Access should be authorized through the application rather than assuming knowledge of an object path is sufficient permission.
Scheduled Jobs
ERP systems contain many scheduled operations:
Daily reconciliation
Invoice reminders
Payment retries
Stock valuation
Report generation
Subscription billing
Data cleanup
Every scheduled job should be:
Tenant-aware
Idempotent
Retryable
Observable
Auditable where appropriate
A job should never assume:
"if it runs twice, nothing bad happens."
Instead, the architecture should deliberately make duplicate execution safe.
ERP Control Plane vs Operational Plane
A useful architectural distinction is:
BizNex OS
│
┌────────────┴────────────┐
▼ ▼
Control Plane Operational Plane
│ │
├── Identity ├── Sales
├── RBAC ├── Inventory
├── Policies ├── Procurement
├── Configuration ├── Billing
└── Workflows └── Accounting
The control plane determines:
Who can do what, under which configuration.
The operational plane records:
What the business actually did.
This separation makes the system easier to reason about.
Complete Sale Architecture
Consider a POS transaction:
Customer buys 3 units of SKU-1029 for $300.
The complete architecture becomes:
POS
│
▼
API Gateway
│
▼
Authentication
│
▼
Tenant Context
│
▼
Sales Service
│
▼
Validate Product
│
▼
Calculate Price
│
▼
Check Availability
│
▼
Reserve Inventory
│
▼
Create Order
│
▼
Create Invoice
│
▼
Process Payment
│
▼
Confirm Transaction
│
┌───────────┴───────────┐
▼ ▼
Inventory Movement Accounting Journal
│ │
└───────────┬───────────┘
▼
Audit Event
│
▼
Outbox Event
│
▼
Background
Processing
The entire operation is not merely a "checkout request."
It is a coordinated business transaction.
What Happens When the Sale Fails?
Suppose:
Order Created
│
Inventory Reserved
│
Payment Fails
The system must define the business compensation:
Payment Failed
│
▼
Release Reservation
│
▼
Cancel / Keep Order Pending
│
▼
Audit Event
The exact workflow depends on the business model.
The important point is that failure behavior is explicitly modeled.
Complete Procurement Architecture
Purchase Request
│
▼
Authorization Check
│
▼
Approval Workflow
│
▼
Purchase Order
│
▼
Supplier
│
▼
Goods Receipt
│
▼
Inventory Movement
│
▼
Supplier Invoice
│
▼
Three-Way Match
│
▼
Accounts Payable
│
▼
Accounting Journal
│
▼
Payment
This demonstrates why ERP architecture cannot be designed module-by-module without thinking about cross-domain flows.
Complete Accounting Flow
For a $10,000 inventory purchase:
Purchase Order
│
▼
Goods Received
│
▼
Inventory +$10,000
│
▼
Accounts Payable +$10,000
│
▼
Journal Posted
Journal:
Debit
Inventory Asset 10,000
Credit
Accounts Payable 10,000
Later, when payment occurs:
Debit
Accounts Payable 10,000
Credit
Cash 10,000
The financial state is therefore reconstructable from journal history.
Architecture Invariants
BizNex should define non-negotiable invariants.
Invariant 1
Every posted journal is balanced.
Invariant 2
Posted financial history is never overwritten.
Invariant 3
Every inventory state change produces a movement.
Invariant 4
A reservation cannot exceed available inventory.
Invariant 5
Every tenant-owned record belongs to exactly one organization.
Invariant 6
Cross-domain state changes use explicit service interfaces.
Invariant 7
Financial operations are idempotent.
Invariant 8
Closed accounting periods cannot be silently modified.
Invariant 9
Historical transactions preserve their original commercial facts.
Invariant 10
Business-critical events are recoverable from the outbox.
Invariant 11
Authorization is enforced server-side.
Invariant 12
Reporting cannot become the authoritative source of financial truth.
These invariants should become automated tests.
They are architectural contracts.
Testing Architecture
ERP testing should operate at multiple levels.
Unit Tests
│
▼
Domain Tests
│
▼
Integration Tests
│
▼
Transaction Tests
│
▼
Authorization Tests
│
▼
Reconciliation Tests
│
▼
End-to-End Workflows
For example:
Test:
Sale of 10 units
Verify:
Order = Confirmed
Inventory = Reserved / Deducted correctly
Invoice = Created
Payment = Correct
Journal = Balanced
Audit = Created
Outbox = Created
One business workflow should therefore validate the entire consistency chain.
Deployment Architecture
A practical initial deployment:
Internet
│
▼
WAF / CDN
│
▼
Load Balancer
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Django-1 Django-2 Django-3
│ │ │
└─────────────┼─────────────┘
│
┌─────────────┼──────────────┐
▼ ▼ ▼
PostgreSQL Redis Celery
│ │
│ ▼
│ Worker Pool
│
▼
Object Storage
The Django application instances should remain horizontally scalable.
PostgreSQL remains the authoritative transactional store.
Redis accelerates non-authoritative state.
Celery handles asynchronous workloads.
Database Strategy
The initial architecture should prefer one PostgreSQL cluster with clear domain ownership.
PostgreSQL
│
├── Identity Tables
├── Sales Tables
├── Inventory Tables
├── Procurement Tables
├── Accounting Tables
└── Audit / Outbox Tables
The logical boundaries are stronger than the physical table separation.
As scale increases:
One PostgreSQL
│
▼
Read Replicas
│
▼
Partitioning
│
▼
Domain-specific storage
Only move toward separate databases when operational or scaling requirements justify the complexity.
High Availability
Because BizNex becomes the operational backbone of a company:
BizNex Down
│
├── Sales Stops
├── Purchasing Stops
├── Inventory Stops
├── Billing Stops
└── Reporting Degrades
Therefore high availability matters.
The architecture should eventually support:
Multiple Application Instances
│
▼
Load Balancer
│
▼
Highly Available PostgreSQL
│
▼
Redis HA
│
▼
Multiple Celery Workers
But high availability should not be introduced by sacrificing transactional correctness.
Disaster Recovery
Critical ERP data includes:
Journal Entries
Inventory Movements
Orders
Invoices
Payments
Purchase Orders
Tenant Configuration
Audit Events
Backups should therefore be:
Encrypted
Automated
Versioned
Access-controlled
Off-site
Regularly restored in testing
The recovery plan should explicitly define:
RPO
Recovery Point Objective
RTO
Recovery Time Objective
For a financial system, those numbers should be business decisions, not merely infrastructure defaults.
Security and Financial Integrity
ERP security is not only about preventing unauthorized login.
It is about preventing unauthorized state transitions.
For example:
Unauthorized User
│
▼
Cannot approve PO
│
▼
Cannot create payable commitment
│
▼
Cannot trigger financial liability
Authorization therefore protects business integrity.
Separation of Duties
Enterprise ERP systems often require separation of duties.
For example:
Employee A
Creates Purchase Order
Employee B
Approves Purchase Order
Employee C
Processes Payment
The same person should not necessarily be able to perform all three actions.
The authorization engine can enforce:
creator != approver
approver != payment_processor
for sensitive workflows.
This turns RBAC into an actual governance mechanism rather than simply hiding UI buttons.
Approval Policy Architecture
Approval rules should eventually become configurable.
Example:
Purchase Amount
│
▼
< $1,000
│
▼
Manager Approval
$1,000 - $10,000
│
▼
Manager + Finance
> $10,000
│
▼
Manager + Finance + Director
The workflow engine should evaluate:
Amount
Organization
Department
Requester
Supplier
Risk Level
Budget
rather than hardcoding every threshold inside controller code.
Budget Control
Procurement can eventually integrate budget management.
Department Budget
│
▼
Purchase Request
│
▼
Committed Amount
│
▼
Available Budget
Example:
Annual Budget $100,000
Committed $72,000
Actual Spend $18,000
Available $10,000
A purchase request exceeding available budget can require escalation or rejection.
This is where procurement, accounting, and organizational policy converge.
ERP as a System of Record
The architecture ultimately produces several authoritative ledgers:
Financial Truth
└── Accounting Ledger
Inventory Truth
└── Stock Movement Ledger
Commercial Truth
└── Orders / Invoices
Procurement Truth
└── Purchase Lifecycle
Identity Truth
└── Organization / Membership / Authorization
Reports are projections of these truths.
Dashboards are projections.
Caches are projections.
Search indexes are projections.
The source of truth remains the underlying transactional domain.
Architecture Evolution
The long-term architecture can evolve without abandoning the initial design.
Phase 1
Modular Monolith
│
▼
Explicit Domain Boundaries
│
▼
Transactional Outbox
│
▼
Domain Events
│
▼
Reporting Projections
│
▼
Async Workflows
│
▼
Independently Scalable Domains
│
▼
Distributed Services
│
▼
Saga / Compensation
The important point is that microservices are an evolution of the architecture, not the architecture itself.
What I Would Build Today
If I were building BizNex OS today, I would start with:
Django
│
├── Explicit domain modules
├── Domain services
├── Application services
├── Repository boundaries
└── Authorization policies
PostgreSQL
│
├── Transactional system of record
├── Accounting ledger
├── Inventory movements
└── Outbox
Redis
│
├── Cache
├── Rate limiting
└── Short-lived state
Celery
│
├── Reports
├── Notifications
├── Reconciliation
└── Async workflows
Object Storage
│
├── Documents
├── Reports
└── Generated artifacts
The architecture would remain a modular monolith until there is a measurable reason to distribute a domain.
What I Would Avoid Initially
Avoid Microservices Everywhere
Sales Service
Inventory Service
Accounting Service
Billing Service
Procurement Service
before the business has enough scale to justify the operational complexity.
Avoid Mutable Financial Balances as Truth
account.balance += amount
should never be the fundamental accounting mechanism.
Avoid Mutable Inventory as the Only Source of Truth
product.stock_quantity = 47
without a movement history makes reconciliation difficult.
Avoid Direct Cross-Domain Database Access
Sales
│
└── UPDATE inventory_stock
creates hidden coupling.
Prefer:
Sales
│
▼
Inventory Service
Avoid Synchronous External Dependencies in Core Transactions
Do not make:
Create Order
│
▼
Send Email
│
▼
Generate PDF
│
▼
Call External ERP
│
▼
Commit Database
the critical transaction path.
Instead:
Core Transaction
│
▼
Commit
│
▼
Outbox
│
▼
Async Workers
The Architectural Contract
The most important output of BizNex OS is not an invoice.
It is consistent business state.
A business operation should be able to answer:
What happened?
│
▼
Which organization?
│
▼
Which user initiated it?
│
▼
Which domain owns the operation?
│
▼
Which inventory changed?
│
▼
Which financial accounts changed?
│
▼
Which documents were created?
│
▼
Which events were emitted?
│
▼
Can the entire operation be reconstructed?
That is what makes an ERP trustworthy.
Final Architecture Principle
BizNex OS is not fundamentally an inventory application, accounting application, POS, or procurement application.
It is a business transaction system in which those domains operate against a shared set of authoritative facts.
BizNex OS
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
Sales Inventory Procurement
│ │ │
└────────────────────┼────────────────────┘
▼
Accounting
│
▼
Business Truth
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
Reporting Audit Operations
The deepest architectural principle is:
Every important business fact should have one authoritative owner, every state transition should preserve that domain's invariants, and every cross-domain workflow should be explicitly orchestrated and traceable.


