Files
zero-fucks-given/.hermes/plans/2026-09-01_144533-erp-foundation-next-steps.md
2026-09-02 09:19:43 +03:00

171 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.