update
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# ERP Foundation Next Steps Implementation Plan
|
||||
|
||||
> **For Hermes:** Implement this plan task-by-task using strict TDD.
|
||||
|
||||
**Goal:** Establish a testable, tenant-safe ERP foundation in the existing AdonisJS 7 + React + PostgreSQL modular monolith, then deliver the first reusable core modules.
|
||||
|
||||
**Architecture:** Preserve the existing `apps/backend/modules/<Module>` and `apps/admin/src/modules/<Module>` conventions. Core infrastructure becomes the only source of tenant scope, authorization, contacts, custom-field metadata, durable events, and internal service contracts. Business modules must not issue SQL joins into each other’s tables.
|
||||
|
||||
**Tech Stack:** AdonisJS 7, Lucid ORM, PostgreSQL 17, Japa, Bouncer, `@adonisjs/auth`, adonis-autoswagger, React 19, Vite, React Query, React Hook Form, React Intl, Docker Compose.
|
||||
|
||||
---
|
||||
|
||||
## Current verified context
|
||||
|
||||
- Compose stack is running with `zfg-*` container names.
|
||||
- Backend lint, formatting, and typecheck run successfully in `zfg-backend-adonis`.
|
||||
- Database-backed tests require `DB_DATABASE=adonis_test`; the default Compose development database is `adonis` and is deliberately rejected by the test bootstrap.
|
||||
- The module generator now generates protected API CRUD routes, policy checks, concrete AutoSwagger response annotations, and CRUD/auth/permission test scaffolding.
|
||||
- The generator must be used as a scaffolding baseline only. ERP domain logic must add a migration, service contract, tenant scope, event/outbox behavior, endpoint tests, and documentation before the module is complete.
|
||||
|
||||
## Definition of done for every ERP endpoint
|
||||
|
||||
1. Write a failing Japa HTTP test first; run it in Compose against `adonis_test` and confirm the expected failure.
|
||||
2. Implement the smallest behavior needed to pass.
|
||||
3. Cover success, authentication, authorization, validation, not-found/conflict, tenant isolation, and any idempotency behavior relevant to that endpoint.
|
||||
4. Annotate every active controller action with concrete, valid JSON AutoSwagger request/response examples.
|
||||
5. Run:
|
||||
- `docker compose exec -T backend npm run lint`
|
||||
- `docker compose exec -T backend npm run typecheck`
|
||||
- `docker compose exec -T backend npm run format`
|
||||
- `docker compose exec -T -e DB_DATABASE=adonis_test backend npm test -- --files <spec>`
|
||||
6. Ensure `git diff --check` passes. For new API routes, inspect generated docs at `/swagger` or run the project’s Swagger command if one is added.
|
||||
|
||||
## Step 1: Make the Compose test database invocation first-class
|
||||
|
||||
**Objective:** Remove the need for ad-hoc environment overrides when running backend tests in Compose.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Modify: `docker-compose.yml`
|
||||
- Modify or create: `apps/backend/.env.test.example`
|
||||
- Modify: `apps/backend/package.json`
|
||||
- Test: `apps/backend/tests/bootstrap.ts`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Decide and document one supported Compose test command, preferably a dedicated `test:compose` backend script or Compose service/profile.
|
||||
2. Ensure it injects `NODE_ENV=test` and `DB_DATABASE=adonis_test` without altering the development backend container’s `adonis` database.
|
||||
3. Add a small test/bootstrap assertion that the test command receives an `*_test` database name.
|
||||
4. Verify the generator test runs without manually supplying `-e DB_DATABASE=adonis_test`.
|
||||
|
||||
## Step 2: Design and implement tenant context
|
||||
|
||||
**Objective:** Resolve one authenticated/request-scoped tenant and prevent unscoped tenant data access.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Create: `apps/backend/database/migrations/<timestamp>_create_tenants_table.ts`
|
||||
- Create: `apps/backend/app/base_modules/Tenant/model/tenant.ts`
|
||||
- Create: `apps/backend/app/services/tenant_context.ts`
|
||||
- Create: `apps/backend/app/middleware/resolve_tenant_middleware.ts`
|
||||
- Modify: `apps/backend/start/kernel.ts`
|
||||
- Modify: `apps/backend/config/database.ts`
|
||||
- Test: `apps/backend/tests/functional/tenant/tenant_context.spec.ts`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Write failing tests for subdomain/header/token tenant resolution, missing tenant rejection, and cross-tenant record invisibility.
|
||||
2. Add a forward-only tenants migration and a tenant-owned record fixture.
|
||||
3. Implement a request-scoped tenant context that is immutable after middleware resolution.
|
||||
4. Add tenant middleware after authentication and before module controllers.
|
||||
5. Introduce a reusable tenant query helper/service so every tenant-owned module uses the same scope.
|
||||
6. Keep the initial approach PostgreSQL row isolation with `tenant_id`; defer per-schema/per-database selection until a measured SaaS scaling requirement exists.
|
||||
|
||||
## Step 3: Extend IAM for tenant-aware ERP permissions
|
||||
|
||||
**Objective:** Build authorization around tenant membership and namespaced ERP permissions.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Create migration(s) for tenant memberships and role assignment scope.
|
||||
- Modify: `apps/backend/app/services/permission_service.ts`
|
||||
- Modify: `apps/backend/app/middleware/initialize_bouncer_middleware.ts`
|
||||
- Create: `apps/backend/app/base_modules/Tenant/policy/tenant_policy.ts`
|
||||
- Test: `apps/backend/tests/functional/iam/tenant_permissions.spec.ts`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Write failing endpoint tests for no token (401), membership absence (403), missing permission (403), and valid tenant role access (200/201).
|
||||
2. Add tenant membership persistence and tenant-scoped role/permission lookup without breaking the existing administrator model.
|
||||
3. Register core permission slugs: `tenant.view`, `tenant.manage`, `contacts.view`, `contacts.create`, `contacts.update`, and `contacts.delete`.
|
||||
4. Add an idempotent migration to grant approved core permissions to the Super Admin role on existing installations.
|
||||
5. Add concrete AutoSwagger examples for all IAM endpoints.
|
||||
|
||||
## Step 4: Implement Base Contacts as the first generated ERP module
|
||||
|
||||
**Objective:** Provide the canonical tenant-owned people and organization directory.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Create: `apps/backend/database/migrations/<timestamp>_create_contacts_table.ts`
|
||||
- Create: `apps/backend/modules/Contacts/...`
|
||||
- Create: `apps/admin/src/modules/Contacts/...`
|
||||
- Test: `apps/backend/tests/modules/Contacts/contacts.api.spec.ts`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Start with HTTP tests for list/create/show/update/delete, validation, permissions, and isolation across two tenants.
|
||||
2. Create `contacts` with tenant ID, contact kind, legal/display name, parent organization, email/phone, address payload, status, timestamps, and `custom_fields JSONB`.
|
||||
3. Generate CRUD assets through MakeModuleController only after the table migration exists; then replace generic controller logic with tenant-aware Contacts service calls.
|
||||
4. Register the Contacts permission config and ensure menus/routes use `contacts.view`.
|
||||
5. Add React module configuration, API client, list/form pages, and translations for `en`, `de`, `fr`, and `nl` with identical key sets.
|
||||
6. Add concrete AutoSwagger examples matching the actual serializer wrappers and error shapes.
|
||||
|
||||
## Step 5: Implement metadata and custom-fields configuration
|
||||
|
||||
**Objective:** Allow tenant administrators to define validated dynamic fields for approved entities without schema migrations.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Create migration(s) for `custom_field_definitions` and optional option records.
|
||||
- Create: `apps/backend/app/base_modules/Metadata/...`
|
||||
- Modify: `apps/backend/modules/Contacts/...`
|
||||
- Test: `apps/backend/tests/functional/metadata/custom_fields.spec.ts`
|
||||
- Create: `apps/admin/src/modules/Metadata/...`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Write failing tests for field creation, allowed target entity, invalid values, required-field enforcement, and tenant isolation.
|
||||
2. Persist field key, entity type, scalar type, validation rules, required/visibility state, option values, and tenant ownership.
|
||||
3. Build one reusable validator that reads metadata definitions and validates the `custom_fields` JSONB payload before module persistence.
|
||||
4. Expose dynamic-form metadata through an authenticated endpoint with concrete AutoSwagger examples.
|
||||
5. Render dynamic controls in the Contacts admin form, including all requested translations.
|
||||
|
||||
## Step 6: Establish the internal event/outbox contract before commercial modules
|
||||
|
||||
**Objective:** Make CRM, Sales, Inventory, Procurement, Invoicing, and Accounting communication durable and module-boundary-safe.
|
||||
|
||||
**Files likely to change:**
|
||||
|
||||
- Create migration: `apps/backend/database/migrations/<timestamp>_create_outbox_events_table.ts`
|
||||
- Create: `apps/backend/app/services/outbox/...`
|
||||
- Create: `apps/backend/app/services/module_contracts/...`
|
||||
- Test: `apps/backend/tests/functional/outbox/outbox_delivery.spec.ts`
|
||||
|
||||
**Tasks:**
|
||||
|
||||
1. Write failing tests proving that transaction rollback creates neither business record nor outbox event, and retries do not process a duplicate event twice.
|
||||
2. Persist event ID, tenant ID, name, version, aggregate ID, correlation ID, payload, occurred time, and processing state in an outbox table within the business transaction.
|
||||
3. Implement an explicit publisher and idempotent consumer registry. The initial delivery mechanism may use the Adonis event emitter only after outbox persistence succeeds.
|
||||
4. Define versioned service interfaces for cross-module reads, beginning with `ContactService` and future `ProductService`; prohibit SQL joins across module-owned tables.
|
||||
5. Add tests and concrete OpenAPI examples for any operational event-monitoring endpoints.
|
||||
|
||||
## After the foundation
|
||||
|
||||
Proceed in this order:
|
||||
|
||||
1. Product Catalog.
|
||||
2. CRM with `crm:lead_won` outbox event.
|
||||
3. Sales Orders consuming `crm:lead_won` and emitting `sales:order_confirmed`.
|
||||
4. Inventory/Logistics consuming confirmed orders and emitting `inventory:stock_shipped`.
|
||||
5. Procurement and reorder rules emitting/consuming receiving events.
|
||||
6. Invoicing and Accounting from immutable operational events.
|
||||
|
||||
## Risks and decisions
|
||||
|
||||
- Do not implement PostgreSQL-per-tenant schemas or separate databases until tenant row isolation, authorization, and test coverage are established; otherwise every migration and test path becomes unnecessarily complex.
|
||||
- Do not add Vite Module Federation until the existing admin module registry is insufficient. The existing React module convention is the source of truth for the first ERP modules.
|
||||
- Do not generate migrations from an already-existing table automatically. Migrations must be authored and reviewed before MakeModuleController scaffolds a module from that table.
|
||||
- Do not generate generic Swagger schema objects. Every response annotation needs a concrete valid JSON example derived from the serializer.
|
||||
Reference in New Issue
Block a user