← All ERPs

Documentation

How the suite is put together, and which parts you are not free to change. Written for somebody who has to work on one of these apps tomorrow.

What this is

Thirty ERP applications that ship independently and behave like one product.

Each of the 30 modules is its own repository, its own Cloudflare Worker and its own database. There is no shared runtime process and no shared database: HR going down cannot take Payroll with it, and a migration in Finance cannot lock a table Billing is reading.

What they do share is a package — erp-core — carrying the design system, the app shell, the UI kit, authentication, the audit trail, and the comment/document/activity panels every record gets. One change there reaches all thirty apps, which is the point: thirty copies of “post a comment” is thirty places for the tenant scope on the read to be forgotten.

The trade is that the shared package is a real dependency with real blast radius. It is consumed from the workspace with link:../erp-core, so every app builds against the working copy and a break is visible immediately rather than on whoever upgrades next.

Addresses

One hostname per module, all under a single zone.

easymanagex.com              this directory
<slug>.easymanagex.com       each ERP
<slug>.easymanagex.com/api/health   liveness, including the D1 binding

The suite is bound with Cloudflare Workers routes, not custom domains. A zone accepts only 100 Workers custom domains and appneural.com is already at that limit, so a custom-domain deploy there is refused outright. Routes cap at 1000.

A route needs its DNS record to already exist.
Wrangler provisions DNS and TLS for each configured custom domain, so every module hostname is created and attached during deployment.

Anatomy of one ERP

Every app has the same shape, so moving between them costs nothing.

src/
  app/(app)/           screens, one folder per menu item
  app/api/             JSON routes
  actions/             server actions — every write goes through one
  lib/domain/          the rules: state machines, gates, readings
  lib/queries.ts       reads, every one org-scoped in its own where
  lib/schemas/         zod, shape validation only
  lib/guard.ts         which role may do what
  db/schema.ts         Drizzle tables
drizzle/               hand-written SQL migrations, applied by wrangler

The split that matters is domain versus schemas. Zod answers “is this submission well formed”. Whether the move it asks for is legal — can this contract go to signature, may this clause be conceded — is a domain question. Rules stated in both places drift, and it is always the copy in the API route that ends up letting something through.

The shared core

What erp-core gives every app.

ImportWhat it is
@appneural/erp-core/uiButtons, tables, forms, tabs, drawers, filter bars, saved views, and the screen shapes: Workbench, StageBar, ScoreScale, Matrix, ProvenancePanel, KpiRow.
@appneural/erp-core/shellAppShell: topbar, nav rail, module switcher across all thirty apps.
@appneural/erp-core/collaborationCommentThread, DocumentList, RecordActivity and their server actions. Every record gets discussion, attachments and history.
@appneural/erp-core/dbDrizzle client bound to the Worker's D1, plus the shared tables: organizations, users, audit_log, comments, documents.
@appneural/erp-core/authcurrentSession, role checks. The suite runs without sign-in today; the guards are written anyway.
@appneural/erp-core/actionsaudit(), errorUrl/okUrl, change diffing — the plumbing every server action repeats.
@appneural/erp-core/storageR2 helpers. Keys are tenant-prefixed so one org cannot read another's file even if an id leaks.
@appneural/erp-core/registryThe canonical list of all thirty modules — names, groups, accents, addresses.

Screen rules

These are not style preferences. Breaking one produces a screen that looks finished and cannot be worked in.

No disconnected mock screens.
Every menu has working navigation and a real workflow behind it. A screen that renders but goes nowhere is worse than a missing one, because it reads as done.
Every primary entity supports the full loop.
List → search and filter → create → detail → edit → related records → comments and documents → activity → actions. Anything less and the record becomes something people export to a spreadsheet to actually use.
Tables for operational records. Cards only for KPIs.
The job on an operational screen is comparing down a column, which cards make impossible. A card is right when the number is one somebody would act on — if a tile would read 'Total records: 41', that is a table header.
Drawers for quick review, full pages for complex records.
Both are addressed by URL, never by client state, so a view can be linked to, refreshed, and shown to somebody else.
Every corner is square.
All five radius tokens resolve to zero and a global rule holds it, because tokens cannot reach a Tailwind utility used for page layout or a browser's own default on a select.
A blocked action says why.
Gates return a reason, not a boolean. A greyed-out 'Send for signature' that explains nothing becomes a Slack message to whoever owns the process.
No marketing pages, pricing, or decorative dashboards.
A dashboard block earns its place only if it can change what somebody does in the next hour. Everything else is a chart people stop looking at.

Data conventions

The four that are load-bearing.

Every row is tenant-scoped, and every query filters on it in its own where.
Pages compose helpers from lib/queries.ts rather than writing Drizzle inline, so the tenant filter cannot be dropped by a page that only meant to add a sort.
Money is stored in minor units, everywhere.
Values roll up across hierarchies and get invoiced against. Floats drift, and the drift lands in a number somebody bills.
Snapshot anything a record was created against.
Clause text, rates, terms. The library moves to v13 mid-negotiation and the record of what was actually proposed must not move with it.
Derived is recomputed; decided is written down.
Whether two texts differ is a function of the strings. Who decided, when, and why is a fact about a person — and it is the only answer you have when the decision is questioned a year later.

Comments, documents and the audit trail all address a record by the same (entity, entity_id) pair, so a detail page’s three collaboration tabs are three indexed lookups against one shape, and attaching a thread to a new entity costs a descriptor rather than a table.

Building an ERP out

The order that works, from scaffold to something usable.

1  docs/USER-STORIES.md + the product's .dc.html mockup
2  db/schema.ts        real entities, not one flat table
3  drizzle/000N_*.sql  IF NOT EXISTS throughout
4  lib/domain/         state machines as data, gates that return reasons
5  lib/guard.ts        domain roles onto erp-core's Role
6  lib/queries.ts      org-scoped reads
7  lib/schemas/        zod, shape only
8  actions/            re-check the gate; audit every change
9  app/(app)/          lists, 360 detail pages, the signature screen
10 pnpm typecheck && pnpm build

Each product’s mockup defines one signature workflow screen — the thing that app exists to do. Contract’s is clause negotiation; Recruitment’s is interview evaluation; Support’s is the ticket workspace. Build it for real, using the Workbench shape: a queue of work on one side, the item being worked on filling the rest, each pane scrolling on its own.

Run the build, not just the typechecker.
tsc does not catch server-action constraints or module resolution. Both have broken deploys here that typechecked cleanly.

Deploying

Migrations first, always.

pnpm db:remote        apply D1 migrations
pnpm run deploy       build and ship the Worker
pnpm run deploy — never pnpm deploy.
pnpm deploy is a built-in pnpm command and will not run the script. It fails with a workspace error that looks unrelated.
The deploy script does not apply migrations.
Shipping code that expects tables the database does not have gives a live app that errors on every page. Migrate first, and check what a migration does before running it against remote — a table rebuild is a DROP.

CI checks out erp-core alongside the app repo, because the dependency is a link: symlink rather than a published package. Turbopack needs its root expanded to the workspace for the same reason.

The thirty modules

Grouped the way the plan groups them. Each links to its repository.

CORE1

01🏛️ Organization ERPLegal entities, departments, locations and the org tree every other ERP resolves against.organization-microerp

PEOPLE7

02🧲 Recruitment ERPRequisitions, candidate pipeline, interviews and offers.recruitment-microerp
03👥 HR ERPEmployee master data, lifecycle events and documents.hr-microerp
04⏱️ Attendance ERPShifts, check-in/out, leave balances and regularisation.attendance-microerp
05💰 Payroll ERPSalary structures, pay runs, payslips and statutory deductions.payroll-microerp
06📈 Performance ERPGoals, review cycles, calibration and ratings.performance-microerp
07🎓 Learning ERPCourse catalogue, enrolments, completions and certifications.learning-microerp
08🧩 Resource ERPBench, allocations, utilisation and capacity forecasting.resource-microerp

CUSTOMER & REVENUE5

09🎯 CRM ERPLeads, opportunities, pipeline stages and forecasting.crm-microerp
10📝 Presales ERPRFPs, solutioning, estimates and proposal tracking.presales-microerp
11🤝 Customer ERPAccount master, hierarchy, health and relationship history.customer-microerp
12🛟 Support ERPTickets, SLAs, escalations and CSAT.support-microerp
13📜 Contract ERPMSAs, SOWs, renewals, obligations and expiry alerts.contract-microerp

DELIVERY4

14📊 Project ERPProjects, milestones, timesheets, budget and margin.project-microerp
15🔁 SDLC ERPSprints, releases, change requests and traceability.sdlc-microerp
16🧪 QA ERPTest suites, cases, runs, defects and coverage.qa-microerp
17🚀 DevOps ERPEnvironments, pipelines, deployments and rollback history.devops-microerp

FINANCE3

18🏦 Finance ERPChart of accounts, journals, ledgers and period close.finance-microerp
19🧾 Billing ERPInvoices, payment schedules, dunning and receipts.billing-microerp
20💳 Expense ERPClaims, policy checks, approvals and reimbursements.expense-microerp

OPERATIONS5

21🛒 Procurement ERPRequisitions, purchase orders, receipts and three-way match.procurement-microerp
22🏭 Vendor ERPVendor onboarding, compliance documents and scorecards.vendor-microerp
23💻 Asset ERPAsset register, assignment, depreciation and retirement.asset-microerp
24📦 Inventory ERPStock items, warehouses, movements and reorder levels.inventory-microerp
25🏢 Administration ERPFacilities, seating, access cards, travel desk and helpdesk.administration-microerp

GOVERNANCE5

26⚠️ Risk ERPRisk register, scoring, treatment plans and residual risk.risk-microerp
27 Compliance ERPFrameworks, controls, evidence and attestation cycles.compliance-microerp
28🔐 Security ERPIncidents, vulnerabilities, access reviews and policies.security-microerp
29🔍 Audit ERPAudit plans, findings, remediation and verification.audit-microerp
30⚖️ Legal ERPMatters, counsel, disputes, IP and regulatory filings.legal-microerp