Architecture
ERP DLM is a complete ERP built with Python FastAPI + Vue 3 + PrimeVue 4 + PostgreSQL 16, containerised with Docker Compose.
Technology stack
Section titled “Technology stack”| Layer | Technology |
|---|---|
| Backend | Python 3.13, FastAPI, SQLAlchemy 2.0 async, asyncpg, Alembic, Pydantic v2 |
| Frontend | Vue 3 Composition API, TypeScript, PrimeVue 4, Pinia, vue-i18n, Vite |
| Database | PostgreSQL 16 (UUID PKs, soft deletes, timestamps, JSONB) |
| Infrastructure | Docker Compose (postgres, redis, backend, frontend), Caddy reverse proxy |
Repository structure
Section titled “Repository structure”erp-dlm/├── backend/│ └── app/│ ├── core/ # Auth, DB, module_loader, core services│ └── modules/ # ~50 functional modules├── frontend/│ └── src/│ ├── core/ # Layouts, reusable components, API client, tours│ ├── modules/ # Module views│ ├── locales/ # es.json, en.json│ └── router/ # Vue Router with lazy loading├── docs/ # Additional technical documentation└── docs-site/ # This documentation site (Astro + Starlight)Backend modules
Section titled “Backend modules”Each functional module lives in backend/app/modules/<name>/ and typically contains:
__init__.py— may be empty.api.py—router = APIRouter(prefix="", ...).models.py— SQLAlchemy tables.schemas.py— Pydantic models.service.py— business logic.
Module loader
Section titled “Module loader”backend/app/core/module_loader.py scans backend/app/modules/ at startup and auto-registers each module with the prefix /api/{module-name}.
Important rules:
- Each
api.pyrouter must useprefix="". - The loader converts underscores to hyphens:
sales_invoicesis mounted as/api/sales-invoices. - Some modules are excluded from auto-discovery and registered manually in
main.py:bank_reconciliation→/api/bank-reconciliationmcp_server→/api/mcpclient_portal→/api/portalaccounting/reports_api.py→/apisaas_manager→/api/saas
Subdomain multi-tenancy
Section titled “Subdomain multi-tenancy”The system supports multi-tenancy through the Host header.
erp.ovlicuo.com→ default instance.acme-sl.erp.ovlicuo.com→ tenantacme-sl.
The middleware (backend/app/core/tenant_middleware.py) extracts the subdomain, looks it up in the SaaS manager database and creates a cached SQLAlchemy engine for the tenant database. The value request.state.tenant_subdomain is propagated to get_db().
Core services
Section titled “Core services”| Service | File | Responsibility |
|---|---|---|
accounting_service |
backend/app/core/accounting_service.py |
Generates accounting entries from invoices and defines VAT/RE account mapping |
numbering |
backend/app/core/numbering.py |
Automatic document numbering: {SERIES}-{YEAR}-{SEQUENCE:05d} |
pdf_service |
backend/app/core/pdf_service.py |
PDF generation with ReportLab |
stock_service |
backend/app/core/stock_service.py |
Warehouse stock, e-commerce reservations and transfers |
tax_reports |
backend/app/core/tax_reports.py |
Spanish fiscal models: 303, 347, 349, 111, 115, 390, 190, 130 |
document_workflow |
backend/app/core/document_workflow.py |
Conversion between sales/purchase documents |
Authentication and permissions
Section titled “Authentication and permissions”The backend uses JWT:
- Login:
POST /api/auth/loginreturnsaccess_tokenandrefresh_token. - Refresh:
POST /api/auth/refresh. - Profile:
GET /api/auth/me. - Permissions:
GET /api/auth/me/permissions.
For external integrations there is an api_tokens module, which allows creating tokens with scopes (api_access) and filtering by company_id in multi-tenant environments.
Frontend conventions
Section titled “Frontend conventions”- Views orchestrate pages, Components receive props and emit events, Composables hold logic.
apiClienthasbaseURL: '/api': calls use routes without duplicating the prefix (apiClient.get('/agents')).- PrimeVue 4: use
Select(notDropdown),ToggleSwitch(notInputSwitch) andTabs/TabList/Tab(notTabView). - Every new router view under
MainLayoutrequires a tour (core/tours/<routeName>.ts), registry entry (core/tours/registry.ts), guide section (core/guide/guide-content.ts) and i18n keys in bothes.jsonanden.json.