lambda-erp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. lambda_erp-0.1.0/.gitignore +29 -0
  2. lambda_erp-0.1.0/LICENSE +21 -0
  3. lambda_erp-0.1.0/PKG-INFO +454 -0
  4. lambda_erp-0.1.0/README.md +430 -0
  5. lambda_erp-0.1.0/api/__init__.py +0 -0
  6. lambda_erp-0.1.0/api/attachments.py +229 -0
  7. lambda_erp-0.1.0/api/auth.py +511 -0
  8. lambda_erp-0.1.0/api/bootstrap.py +498 -0
  9. lambda_erp-0.1.0/api/chat.py +2764 -0
  10. lambda_erp-0.1.0/api/demo_limits.py +400 -0
  11. lambda_erp-0.1.0/api/deps.py +7 -0
  12. lambda_erp-0.1.0/api/errors.py +56 -0
  13. lambda_erp-0.1.0/api/main.py +182 -0
  14. lambda_erp-0.1.0/api/pdf.py +151 -0
  15. lambda_erp-0.1.0/api/providers.py +116 -0
  16. lambda_erp-0.1.0/api/routers/__init__.py +0 -0
  17. lambda_erp-0.1.0/api/routers/accounting.py +63 -0
  18. lambda_erp-0.1.0/api/routers/admin.py +122 -0
  19. lambda_erp-0.1.0/api/routers/analytics.py +1009 -0
  20. lambda_erp-0.1.0/api/routers/bank_reconciliation.py +31 -0
  21. lambda_erp-0.1.0/api/routers/documents.py +100 -0
  22. lambda_erp-0.1.0/api/routers/masters.py +396 -0
  23. lambda_erp-0.1.0/api/routers/reports.py +735 -0
  24. lambda_erp-0.1.0/api/routers/setup.py +387 -0
  25. lambda_erp-0.1.0/api/services.py +372 -0
  26. lambda_erp-0.1.0/api/templates/document.html +197 -0
  27. lambda_erp-0.1.0/docs/agents/README.md +43 -0
  28. lambda_erp-0.1.0/frontend/README.md +84 -0
  29. lambda_erp-0.1.0/frontend/src/api/client.ts +399 -0
  30. lambda_erp-0.1.0/lambda_erp/__init__.py +3 -0
  31. lambda_erp-0.1.0/lambda_erp/accounting/__init__.py +0 -0
  32. lambda_erp-0.1.0/lambda_erp/accounting/bank_transaction.py +76 -0
  33. lambda_erp-0.1.0/lambda_erp/accounting/budget.py +117 -0
  34. lambda_erp-0.1.0/lambda_erp/accounting/chart_of_accounts.py +183 -0
  35. lambda_erp-0.1.0/lambda_erp/accounting/general_ledger.py +362 -0
  36. lambda_erp-0.1.0/lambda_erp/accounting/journal_entry.py +235 -0
  37. lambda_erp-0.1.0/lambda_erp/accounting/payment_entry.py +515 -0
  38. lambda_erp-0.1.0/lambda_erp/accounting/pos_invoice.py +342 -0
  39. lambda_erp-0.1.0/lambda_erp/accounting/purchase_invoice.py +504 -0
  40. lambda_erp-0.1.0/lambda_erp/accounting/revaluation.py +172 -0
  41. lambda_erp-0.1.0/lambda_erp/accounting/sales_invoice.py +523 -0
  42. lambda_erp-0.1.0/lambda_erp/accounting/subscription.py +132 -0
  43. lambda_erp-0.1.0/lambda_erp/buying/__init__.py +0 -0
  44. lambda_erp-0.1.0/lambda_erp/buying/purchase_order.py +165 -0
  45. lambda_erp-0.1.0/lambda_erp/controllers/__init__.py +0 -0
  46. lambda_erp-0.1.0/lambda_erp/controllers/currency.py +52 -0
  47. lambda_erp-0.1.0/lambda_erp/controllers/defaults.py +51 -0
  48. lambda_erp-0.1.0/lambda_erp/controllers/pricing_rule.py +103 -0
  49. lambda_erp-0.1.0/lambda_erp/controllers/taxes_and_totals.py +369 -0
  50. lambda_erp-0.1.0/lambda_erp/database.py +1543 -0
  51. lambda_erp-0.1.0/lambda_erp/exceptions.py +37 -0
  52. lambda_erp-0.1.0/lambda_erp/hooks.py +37 -0
  53. lambda_erp-0.1.0/lambda_erp/model.py +462 -0
  54. lambda_erp-0.1.0/lambda_erp/selling/__init__.py +0 -0
  55. lambda_erp-0.1.0/lambda_erp/selling/quotation.py +263 -0
  56. lambda_erp-0.1.0/lambda_erp/selling/sales_order.py +214 -0
  57. lambda_erp-0.1.0/lambda_erp/simulation.py +704 -0
  58. lambda_erp-0.1.0/lambda_erp/stock/__init__.py +0 -0
  59. lambda_erp-0.1.0/lambda_erp/stock/delivery_note.py +254 -0
  60. lambda_erp-0.1.0/lambda_erp/stock/purchase_receipt.py +356 -0
  61. lambda_erp-0.1.0/lambda_erp/stock/stock_entry.py +330 -0
  62. lambda_erp-0.1.0/lambda_erp/stock/stock_ledger.py +337 -0
  63. lambda_erp-0.1.0/lambda_erp/utils.py +167 -0
  64. lambda_erp-0.1.0/pyproject.toml +40 -0
  65. lambda_erp-0.1.0/terraform/README.md +293 -0
@@ -0,0 +1,29 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.so
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+ *.db
9
+ *.db-shm
10
+ *.db-wal
11
+ *.sqlite3
12
+ .venv/
13
+ venv/
14
+ .env
15
+ .env.*
16
+
17
+ # JWT signing secret auto-generated by api/auth.py when JWT_SECRET_KEY is
18
+ # unset. Forging login cookies is trivial if this leaks.
19
+ .jwt_secret
20
+ *.log
21
+ .idea/
22
+ .vscode/
23
+ *.swp
24
+ *~
25
+ .DS_Store
26
+ Thumbs.db
27
+ frontend/node_modules/
28
+ frontend/dist/
29
+ *.tgz
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TORUS INVESTMENTS AG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,454 @@
1
+ Metadata-Version: 2.4
2
+ Name: lambda-erp
3
+ Version: 0.1.0
4
+ Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
+ Author: TORUS INVESTMENTS AG
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: anthropic>=0.40
10
+ Requires-Dist: bcrypt>=5
11
+ Requires-Dist: fastapi>=0.115
12
+ Requires-Dist: holidays>=0.40
13
+ Requires-Dist: httpx>=0.27
14
+ Requires-Dist: jinja2>=3.1
15
+ Requires-Dist: openai>=1.0
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: python-dateutil>=2.8
18
+ Requires-Dist: python-dotenv>=1.0
19
+ Requires-Dist: python-jose[cryptography]>=3.3
20
+ Requires-Dist: python-multipart>=0.0.9
21
+ Requires-Dist: uvicorn[standard]>=0.30
22
+ Requires-Dist: weasyprint>=62.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Lambda ERP
26
+
27
+ **Open-source ERP you can run through chat — configurable in plain language**
28
+
29
+ Lambda ERP is a working prototype of a simpler ERP: create invoices, check inventory, answer accounting questions, and change reports by asking for what you need in plain language.
30
+
31
+ https://github.com/user-attachments/assets/1b2749ef-10e7-42f5-9cce-df5628292667
32
+
33
+ <p align="center">
34
+ <a href="https://lambda-erp-demo.grayocean-53ec71ac.northeurope.azurecontainerapps.io/demo">
35
+ <img alt="Try the Live Demo" src="https://img.shields.io/badge/%E2%96%B6%20Try%20the%20Live%20Demo-4fc3f7?style=for-the-badge&labelColor=000000">
36
+ </a>
37
+ </p>
38
+
39
+ > Click **Enter Live Demo** for a 40-second scripted walkthrough and prompt freely after (rate-limited to ~$50/day of LLM spend across all visitors).
40
+
41
+ **Join the discussion** on Discord — report bugs, share prompts that broke (or surprised) the agent, or just see what other early users are building:
42
+
43
+ <p>
44
+ <a href="https://discord.gg/ZwFh9hZJTb"><img alt="Join the Lambda ERP Discord" src="https://img.shields.io/discord/1496911123029557359?color=7289DA&label=Join%20on%20Discord&logo=discord&logoColor=white&style=for-the-badge"></a>
45
+ </p>
46
+
47
+ This release is not yet production-ready. It's a complete prototype but not vetted enough to run your payroll on it yet.
48
+
49
+ ---
50
+
51
+ ## Why another ERP?
52
+
53
+ Today, the bulk of an ERP rollout isn't the software license — it's everything that has to happen on top of it before the system actually fits the company. For a small or mid-sized company, the license is typically a few thousand a year, while getting it set up routinely runs **$10-50k** — many times the annual license spend before anyone has logged in. The system also keeps evolving after go-live — each custom report, country-specific tax rule, or workflow change is its own round of work. Larger systems like Oracle NetSuite and Microsoft Dynamics follow the same shape, and S/4HANA-class projects run into the millions.
54
+
55
+ ERPs cost what they cost because they're generic platforms that have to be bent into the shape of each company. The work is configuration, workflow design, custom reports, data migration, integrations — language and structure work. It's been slow and manual because software couldn't do it. Until recently.
56
+
57
+ We think that's about to flip. The bulk of an ERP implementation is text-transformation and configuration that LLMs are now genuinely good at:
58
+
59
+ - **Reading documents.** A supplier PDF becomes a Purchase Invoice. A bank statement becomes reconciled journal entries. An onboarding form becomes a Customer record.
60
+ - **Chart of Accounts design and mapping.** Taking a client's legacy accounts, translating to local GAAP, producing the mapping table - pure language-plus-structure work.
61
+ - **Master data migration.** Cleaning, deduplicating, and loading customer/supplier/item masters and opening balances from whatever mess the legacy system coughs up.
62
+ - **Custom reports and print formats.** The bespoke chart, the specific invoice layout, the cash-flow view nobody else has - unbounded client taste, infinite long tail.
63
+ - **End-user training and ongoing questions.** "How do I issue a credit note?" "Why is this balance off?" "What was last quarter's margin by product line?" - natural-language lookups that used to need a help-desk hour.
64
+
65
+ Each of those is hours of skilled work today. With a capable LLM in the loop, the same task takes seconds of compute and a review pass from someone who knows the business. The software starts tailoring itself to you, instead of the other way around. And because Lambda ERP is open source and self-hosted, the configuration doesn't stop at go-live — the system can keep evolving alongside the company. For implementation partners, the shape of the work shifts: less time spent hand-writing every change, more time spent on the judgment calls — chart-of-accounts design, compliance, change management — that actually need a human.
66
+
67
+
68
+ ---
69
+
70
+ ## How it works
71
+
72
+ ```
73
+ ┌────────────────────────────┐ ┌────────────────────────────┐
74
+ │ React + Vite frontend │◄──►│ FastAPI backend │
75
+ │ - Document forms │ │ - Document CRUD API │
76
+ │ - Reports / Analytics │ │ - Report endpoints │
77
+ │ - Chat (WebSocket) │ │ - Auth (JWT cookie) │
78
+ │ - Client-side JS runtime │ │ - WebSocket chat gateway │
79
+ │ (Web Worker) for charts │ └────────────┬───────────────┘
80
+ └────────────────────────────┘ │
81
+
82
+ ┌──────────────────────────────────────────┐
83
+ │ LLM orchestrator │
84
+ │ - GPT-5.4 drives the reasoning loop │
85
+ │ - Tool-use: document CRUD, search, │
86
+ │ reports, aggregations, analytics │
87
+ │ - Delegates JS generation to Anthropic │
88
+ │ code-specialist sub-agent │
89
+ └──────────────────┬───────────────────────┘
90
+
91
+
92
+ ┌────────────────────────────────────────────┐
93
+ │ lambda_erp/ (pure Python, no framework) │
94
+ │ - Document base class + lifecycle │
95
+ │ - General Ledger (double-entry) │
96
+ │ - Stock Ledger (moving average) │
97
+ │ - Tax calculation engine │
98
+ │ - Pluggable DB backend (SQLite / Postgres)│
99
+ └────────────────────────────────────────────┘
100
+ ```
101
+
102
+ **Key design choices:**
103
+
104
+ - **Chat-first, not chat-bolted-on.** The chat isn't a copilot sidebar - it's the primary way to interact with the system. Every document type, every report, every master record is reachable from tool-use.
105
+ - **One shape for every document.** Invoices, sales orders, stock entries, payments - all share a single `Document` base class and the same three-state lifecycle (Draft → Submitted → Cancelled) with `on_submit`/`on_cancel` hooks. The LLM learns the pattern once and drives every doctype the same way. Leading open-source and commercial ERPs have per-model action verbs spread across 150+ core models; each one is a separate tool the model has to get right.
106
+ - **Metadata-driven UI, shared with the LLM.** A single React form component renders every doctype from `frontend/src/lib/doctypes.ts`. The schema the model reasons over and the schema the user sees are literally the same file. Adding a field is two lines - one in the Python class, one in the config - not a new module with hand-written views and inheritance overlays.
107
+ - **Two-model orchestration.** A planner model handles reasoning and tool-use. When it needs to generate code for a custom report, it delegates to a code-specialist sub-agent. This keeps each model doing what it's best at and keeps latency down on simple turns.
108
+ - **Semantic datasets, not free SQL.** The LLM can't write raw SQL; it calls whitelisted semantic datasets (`sales_invoices`, `purchase_invoices`, `ar_open_items`, `stock_balances`, etc.) with whitelisted filters and group-bys. This makes the system auditable without sacrificing flexibility.
109
+ - **Client-side analytics runtime.** Custom report JS executes in a sandboxed Web Worker in the user's browser. The server never runs untrusted JS. Charts are persisted as portable specs, not screenshots.
110
+ - **Double-entry invariant enforced.** Every submitted document that touches the GL must balance to zero. The engine adds round-off entries for rounding gaps and refuses to post imbalanced vouchers.
111
+ - **One deployment per customer, simple to operate.** Lambda ERP is built to be self-hosted by a single company for its own books - not as a multi-tenant SaaS. One FastAPI process, one database, one VPS is enough. If you want a hosted offering, we'll ship a dedicated instance per customer.
112
+
113
+ ### Tech stack at a glance
114
+
115
+ | Layer | What |
116
+ |---|---|
117
+ | Backend business logic | Pure Python, no framework (`lambda_erp/`) |
118
+ | Web API | FastAPI + Pydantic |
119
+ | Storage | SQLite in the prototype; Postgres planned for production |
120
+ | Frontend | React + Vite + TypeScript + Tailwind + Recharts |
121
+ | Chat transport | WebSocket |
122
+ | LLM orchestrator | OpenAI (configurable) |
123
+ | Code specialist | Anthropic (configurable) |
124
+ | Auth | JWT httponly cookie, three roles + demo |
125
+
126
+ ---
127
+
128
+ ## What works today
129
+
130
+ - Full sales and purchase cycles (Quotation → Sales Order → Delivery Note → Sales Invoice → Payment Entry, and the buying equivalents)
131
+ - Returns / credit notes / debit notes with proper GL and stock reversal
132
+ - Moving-average stock ledger with negative-stock protection
133
+ - Double-entry General Ledger with cancellation reversal
134
+ - Preset reports: Trial Balance, Profit & Loss, Balance Sheet, General Ledger, AR/AP Aging, Stock Balance
135
+ - Custom analytics drafts via chat (persisted, shareable, editable)
136
+ - Server-side aggregation tool for in-chat factual answers across large datasets
137
+ - PDF / image attachment → add invoices, create quotations, etc. all directly by adding them in the chat
138
+ - Auth with admin/manager/viewer roles plus a public demo mode
139
+ - Full test suite that exercises every cycle against an in-memory SQLite
140
+
141
+ ## What's still todo (Suggestions welcome)
142
+
143
+ - MCP integration for supplier/customer communication (quotes, orders, confirmations)
144
+ - Multi-currency beyond the simplified current handling
145
+ - Workflows / approval chains
146
+ - Serial & batch tracking
147
+ - Manufacturing (BOM, work orders)
148
+ - HR / Payroll beyond the journal-entry workaround
149
+ - Regional compliance packs (GST, VAT returns, etc.) - see below
150
+ - PDF report creation directly inside the chat
151
+
152
+ ---
153
+
154
+ ## Why now
155
+
156
+ Four things had to be true for this to work, and they all became true in the last ~18 months:
157
+
158
+ 1. **LLMs can reliably call tools.** A year ago, models would hallucinate tool calls, mangle JSON, or drift after 2–3 steps. Today's frontier models can run an 8-step reasoning loop over a real tool inventory without falling off.
159
+ 2. **Costs collapsed.** Generating a custom report via a code-specialist sub-agent is cents of compute. Even keeping a human reviewer fully in the loop, the marginal cost of "one more report" or "one more dashboard" drops by orders of magnitude — which means companies actually ask for them, instead of living with the defaults.
160
+ 3. **Structured output + function calling are first-class.** We can constrain the LLM's outputs to valid tool-call schemas, safe SQL parameters, and typed JSON - which is what makes an AI-native ERP even conceivable as a safe thing to run.
161
+ 4. **Greenfield is finally cheaper than retrofit.** Twenty-year ERP codebases have hundreds of bespoke models and thousands of hand-written forms - teaching an LLM to drive that reliably means curating a custom tool layer over every quirk. Starting from scratch around one Document lifecycle and a metadata-driven UI is now cheaper than retrofitting an existing platform.
162
+
163
+ ---
164
+
165
+ ## Why open source
166
+
167
+ Every company configuring an ERP runs into the same problems: local tax rules, common workflow patterns, industry-specific accounting quirks. Most of that knowledge isn't a competitive advantage — it's the same ground being re-covered separately at every implementation, by every team, in slightly different ways.
168
+
169
+ We want Lambda ERP to be where that knowledge lives in public. The base system is MIT-licensed, and we're organizing the repo so that community contributions - a German VAT pack, a U.S. sales-tax-by-state module, an industry template for professional services - can slot in under `docs/` and be picked up by the LLM as reference material. The goal is a true community where running an ERP gets cheaper, faster, and more flexible for everyone, not just the implementer.
170
+
171
+ ---
172
+
173
+ ## Get it running
174
+
175
+ ### Docker (one command)
176
+
177
+ You need Docker and Compose v2. The canonical installs:
178
+
179
+ - **Mac / Windows**: install [Docker Desktop](https://www.docker.com/products/docker-desktop/) — ships `docker compose` v2 built in.
180
+ - **Linux / WSL**: Docker's own one-liner installs both the engine and the Compose plugin:
181
+ ```bash
182
+ curl -fsSL https://get.docker.com | sh
183
+ sudo usermod -aG docker $USER && newgrp docker
184
+ ```
185
+ (Ubuntu's `docker.io` apt package and Snap's `docker` omit Compose; use the script above instead.)
186
+
187
+ Then, from the repo root:
188
+
189
+ ```bash
190
+ cp .env.example .env # add your OPENAI_API_KEY and ANTHROPIC_API_KEY (for custom analytics)
191
+ docker compose up --build
192
+ ```
193
+
194
+ First boot takes **~2-3 minutes** — the container runs a 3-year historical simulator to populate realistic demo data. Watch the logs; you'll see monthly `[sim]` progress lines and, when it's done, a clear banner:
195
+
196
+ ```
197
+ ======================================================
198
+ Lambda ERP is READY — open http://127.0.0.1:8000 in your browser
199
+ ======================================================
200
+ ```
201
+
202
+ Hitting the URL before that banner will look like the page "hangs" — uvicorn isn't listening yet, so requests queue at the TCP layer until bootstrap finishes. Wait for the banner, then open the URL.
203
+
204
+ > **Use `127.0.0.1`, not `localhost`.** On WSL2 + Docker Desktop, browsers occasionally stall for minutes on WebSocket upgrades to `localhost` even when HTTP works fine. `127.0.0.1` has never been observed to misbehave.
205
+
206
+ The container serves both the UI and the API at the same origin - there's no separate frontend port in Docker mode. You'll land on the login page with **register-your-first-admin** enabled; create an account and that account becomes the admin for your instance.
207
+
208
+ If instead you want the hosted-demo experience (a shared `public_manager` account and the "Enter Live Demo" button), add `LAMBDA_ERP_ENABLE_PUBLIC_DEMO=1` to your `.env`.
209
+
210
+ State persists to a named Docker volume, so subsequent `docker compose up` starts in seconds. `docker compose down -v` wipes the volume to force a fresh re-seed.
211
+
212
+ ### Local dev
213
+
214
+ Requires Python 3.10+ and Node 18+.
215
+
216
+ ```bash
217
+ # Backend
218
+ python3 -m venv .venv
219
+ source .venv/bin/activate
220
+ pip install -e .
221
+ uvicorn api.main:app --reload --port 8000
222
+
223
+ # Frontend (separate terminal)
224
+ cd frontend
225
+ npm install
226
+ npm run dev
227
+ ```
228
+
229
+ Open `http://localhost:5173`. Vite proxies `/api/*` to the backend.
230
+
231
+ ### Environment
232
+
233
+ ```
234
+ OPENAI_API_KEY=sk-...
235
+ ANTHROPIC_API_KEY=sk-ant-... # optional, used for the code-specialist sub-agent
236
+ ANTHROPIC_CODE_MODEL=claude-opus-4-7 # optional, default shown
237
+ ```
238
+
239
+ Chat needs `OPENAI_API_KEY`. Custom-report code generation uses `ANTHROPIC_API_KEY` when set; otherwise it falls back and the chat will tell you it can't generate reports.
240
+
241
+ ### Run the validation suite
242
+
243
+ ```bash
244
+ source .venv/bin/activate
245
+ python -m tests.test_erp_validation
246
+ ```
247
+
248
+ Runs a full cycle in-memory: setup, sales cycle, purchase cycle, returns, submits, cancels, payments, trial balance. No credentials, no network, ~2 seconds.
249
+
250
+ ---
251
+
252
+ ## Repository layout
253
+
254
+ ```
255
+ lambda_erp/ # Pure Python business logic (no framework)
256
+ accounting/ # GL, journal entries, invoices, payments
257
+ selling/ # Quotations, sales orders
258
+ buying/ # Purchase orders
259
+ stock/ # Stock ledger, stock entries
260
+ controllers/ # Tax engine
261
+ api/ # FastAPI + WebSocket chat
262
+ routers/ # REST endpoints
263
+ chat.py # LLM orchestrator, tool schemas, reasoning loop
264
+ frontend/ # React + Vite app
265
+ tests/ # Validation / regression suite
266
+ docs/agents/ # Invariants, gotchas, design decisions (LLM-readable)
267
+ ```
268
+
269
+ `docs/agents/` is worth a read if you're going to contribute - it captures the invariants the code assumes but doesn't always enforce, plus the landmines that have bitten us.
270
+
271
+ ---
272
+
273
+ ## Building a customer deployment on top of the core
274
+
275
+ Lambda ERP is **one deployment per customer** (see the design choices above). A
276
+ customer that needs **core business-logic changes** should **not fork this
277
+ repo**. Instead, create a **separate private repo that depends on this one as
278
+ the core** and overrides behavior at defined seams. Core fixes then arrive via a
279
+ version bump, not a merge into a diverging fork. Full plan and rationale:
280
+ [`docs/core-extension-architecture.md`](docs/core-extension-architecture.md).
281
+
282
+ **Customer repo layout**
283
+
284
+ ```
285
+ acme-erp/
286
+ pyproject.toml # depends on lambda-erp (git / path / PyPI)
287
+ acme/
288
+ plugin.py # register() — wires backend overrides + hooks
289
+ sales_invoice.py # e.g. class AcmeSalesInvoice(SalesInvoice): ...
290
+ frontend/
291
+ package.json # depends on @lambda-development/erp-core
292
+ tailwind.config.ts # scans @lambda-development/erp-core dist + adds its preset
293
+ src/
294
+ plugin.ts # registers frontend overrides (doctypes/routes/nav/branding)
295
+ main.tsx # import plugin + styles, then bootstrap()
296
+ config/ # branding, enabled features, base currency, OAuth
297
+ deploy/ # Dockerfile, env/secrets
298
+ ```
299
+
300
+ **Override core logic (replace) — subclass + register**
301
+
302
+ ```python
303
+ # acme/sales_invoice.py
304
+ from lambda_erp.accounting.sales_invoice import SalesInvoice
305
+ class AcmeSalesInvoice(SalesInvoice):
306
+ def _get_gl_entries(self):
307
+ gl = super()._get_gl_entries()
308
+ # customer-specific posting
309
+ return gl
310
+ ```
311
+
312
+ Registering it makes every loader path (`create/load/update/submit/cancel`) **and
313
+ document conversions** use the subclass.
314
+
315
+ **Add behavior (don't replace) — lifecycle hooks**
316
+
317
+ ```python
318
+ from lambda_erp.hooks import register_hook
319
+ register_hook("Sales Invoice:after_submit", push_to_external_system)
320
+ ```
321
+
322
+ Events are `"<DocType>:{before,after}_{save,submit,cancel}"`. `before_*` run
323
+ **inside** the document's transaction (a raise aborts and rolls back — use for
324
+ guards/validation); `after_*` run **post-commit** (the voucher is durable — use
325
+ for side-effects/integrations).
326
+
327
+ **Wire it up**
328
+
329
+ ```python
330
+ # acme/plugin.py
331
+ from api.services import register_doctype, register_converter
332
+ from lambda_erp.hooks import register_hook
333
+ from .sales_invoice import AcmeSalesInvoice
334
+
335
+ def register():
336
+ register_doctype("Sales Invoice", AcmeSalesInvoice)
337
+ register_hook("Sales Invoice:after_submit", push_to_external_system)
338
+ # register_converter(source, target, fn) # only to replace conversion *logic*
339
+ ```
340
+
341
+ Point the deployment at it with `LAMBDA_ERP_PLUGINS=acme` (comma-separated for
342
+ several). On startup the core imports each module and calls `register()`. Unset
343
+ = the core runs unchanged.
344
+
345
+ **Frontend overrides — the `@lambda-development/erp-core` library**
346
+
347
+ The frontend ships as a library. The customer app depends on it, registers its
348
+ overrides in a plugin module, then boots the shared app shell. The seams mirror
349
+ the backend: add/replace doctypes, routes, nav, whole components, branding, and
350
+ the API base — without editing core files.
351
+
352
+ ```ts
353
+ // frontend/src/plugin.ts — runs before bootstrap()
354
+ import {
355
+ registerDoctype, registerRoute, registerNavGroup, registerComponent,
356
+ configureBranding, configureApiBase,
357
+ } from "@lambda-development/erp-core";
358
+ import AcmeDashboard from "./acme-dashboard";
359
+
360
+ configureApiBase(import.meta.env.VITE_API_BASE ?? "/api");
361
+ configureBranding({ appName: "Acme ERP", tokens: { "--brand": "260 80% 55%" } });
362
+
363
+ registerDoctype({ slug: "service-ticket", label: "Service Ticket", /* …schema… */ });
364
+ registerNavGroup({ label: "Service", icon: null, items: [{ label: "Tickets", path: "/app/service-ticket" }] });
365
+ registerRoute({ path: "reports/sla", element: <SlaReport /> }); // under the app shell
366
+ registerComponent("Dashboard", AcmeDashboard); // swap a core component
367
+ ```
368
+
369
+ ```tsx
370
+ // frontend/src/main.tsx
371
+ import "./plugin"; // register overrides first
372
+ import "@lambda-development/erp-core/styles.css"; // base tokens + layers (your Tailwind processes it)
373
+ import "./acme.css"; // optional: override :root tokens, add utilities
374
+ import { bootstrap } from "@lambda-development/erp-core";
375
+ bootstrap(); // builds routes AFTER registration, then mounts
376
+ ```
377
+
378
+ Styling follows the **"consumer scans source"** model — your app runs Tailwind
379
+ and the library provides the tokens and preset:
380
+
381
+ ```ts
382
+ // frontend/tailwind.config.ts
383
+ import erpPreset from "@lambda-development/erp-core/tailwind-preset";
384
+ export default {
385
+ content: ["./src/**/*.{ts,tsx}", "./node_modules/@lambda-development/erp-core/dist/**/*.js"],
386
+ presets: [erpPreset],
387
+ };
388
+ ```
389
+
390
+ Rebrand by overriding the `:root` CSS variables (`--brand`, `--surface`, `--text`,
391
+ …) — at runtime via `configureBranding({ tokens })` or statically in your own CSS.
392
+
393
+ **Wire up overrides:** registry registration at runtime (above) covers most
394
+ cases. For a build-time whole-module swap, point a Vite `resolve.alias` at your
395
+ replacement file.
396
+
397
+ **Rules**
398
+
399
+ - **Don't edit core files** — override at a seam. If what you need to change
400
+ isn't a seam yet, add the seam to the core (a PR here), then override from the
401
+ customer repo.
402
+ - Keep branding / feature toggles / base currency / auth config in `config`/env,
403
+ not code.
404
+ - Bump the core version to pull fixes; never copy core code in.
405
+
406
+ Both the backend seams (document classes, lifecycle hooks, converters, plugin
407
+ loading) and the frontend seams (doctype/route/nav/component registries,
408
+ branding, configurable API base, Tailwind preset) are implemented. The backend
409
+ also builds a clean pip wheel and the frontend a `@lambda-development/erp-core` npm library
410
+ — see [`docs/packaging-distribution-plan.md`](docs/packaging-distribution-plan.md)
411
+ for the publish path.
412
+
413
+ ---
414
+
415
+ ## Contributing
416
+
417
+ This is early. The project needs:
418
+
419
+ - Country compliance packs (tax rules, invoice formats, mandatory fields)
420
+ - Industry templates (services, retail, light manufacturing, SaaS)
421
+ - More preset reports
422
+ - A Postgres storage adapter (the current SQLite layer is fine for local evaluation but will need to be swapped for real multi-user write loads)
423
+ - Better observability around token spend per turn
424
+ - Native messenger integration, for WhatsApp, Telegram, etc.
425
+
426
+ PRs welcome. File an issue first if it's a big change, or drop by our [Discord](https://discord.gg/ZwFh9hZJTb) to discuss ideas.
427
+
428
+ ---
429
+
430
+ ## License
431
+
432
+ MIT. See [LICENSE](./LICENSE) for the full text.
433
+
434
+ ---
435
+
436
+ ## Changelog
437
+
438
+ Release notes live in [CHANGELOG.md](./CHANGELOG.md). Releases are tagged
439
+ `vX.Y.Z` and published in lockstep to PyPI (`lambda-erp`) and npm
440
+ (`@lambda-development/erp-core`).
441
+
442
+ ---
443
+
444
+ ## Status
445
+
446
+ Version 0. Working prototype that implements the vision. Fine for demos, internal tools, and hacking. Not yet ready to handle your company's actual books - run it alongside your real ERP if you want to kick the tires.
447
+
448
+ If you try it, we'd love to know what broke.
449
+
450
+ ---
451
+
452
+ ## Trademarks and affiliations
453
+
454
+ Lambda ERP and [lambda.dev](https://lambda.dev/) are product and trade names of **TORUS INVESTMENTS AG**. It is not affiliated with, endorsed by, or sponsored by OpenAI, Anthropic, SAP, Oracle, Microsoft, or any other company named in this repository. SAP, Business One, S/4HANA, Oracle, NetSuite, Microsoft, Dynamics, OpenAI, GPT, Anthropic, and Claude are trademarks of their respective owners and are referenced here only for descriptive and comparative purposes (nominative fair use). We interoperate with OpenAI and Anthropic APIs as a customer like anyone else; you supply your own API keys.