erp_be/BACKEND_TASKS.md

467 lines
23 KiB
Markdown
Raw 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 Backend — Development Tasks (Phase 1)
Reference: [BACKEND_SETUP.md](./BACKEND_SETUP.md)
Use this checklist when building or extending the backend. Follow the build order in Section 1.
---
## 1. Project Scaffold
- [x] Create `package.json` with dependencies and scripts from the spec
- [x] Install dependencies (`npm install`)
- [x] Add `.env.example` (never commit `.env`)
- [x] Configure ESLint, Prettier, Husky, lint-staged
- [x] Add `jest.config.js` with `dotenv/config` setup file
---
## 2. Database & Prisma
- [x] Define `prisma/schema.prisma`:
- Auth: `User`, `Role`, `Module`, `Permission`, `RolePermission`, `RefreshToken`, `PasswordResetToken`
- System: `AuditLog`, `DocumentSeries`
- Business: Vendor (+ sub-tables), PurchaseOrder (+ items), GRN (+ items), Asset (+ transfers), all masters
- [x] Use `PascalCase` models, `snake_case` fields, `@@map` to plural table names
- [ ] Add common columns on every business table: `is_active`, `created_by`, `updated_by`, `created_at`, `updated_at`, `deleted_at`
- [ ] Run `npx prisma migrate dev --name init`
- [x] Run `npx prisma generate`
- [x] Implement `prisma/seed.js` — bootstrap Super Admin user (modules/permissions/roles from DDL)
- [ ] Full idempotent seed (modules → permissions → roles → role_permissions) if not using DDL
### Seed requirements
| Step | Content |
|------|---------|
| Modules | `USERS`, `ROLES`, `MASTERS`, `VENDOR`, `PURCHASE_ORDER`, `GRN`, `ASSET`, `SETTINGS`, `AUDIT_LOGS` |
| Permissions | Each module × `view`, `create`, `edit`, `delete`, `approve`, `export` (skip where N/A) |
| Roles | Super Admin, Admin, Purchase Manager, Store Manager, Accounts, Asset Manager |
| Bootstrap user | Super Admin, bcrypt-hashed password, `status = active` |
---
## 3. Core Infrastructure
- [x] `src/config/env.js` — Joi validation; app must refuse to boot on invalid/missing env
- [x] `src/config/logger.js` — Winston + daily rotate; redact sensitive keys
- [x] `src/config/morgan.js` — HTTP logs into Winston with request ID + user
- [x] `src/config/prisma.js` — singleton client with query/error logging
- [x] `src/config/swagger.js` — OpenAPI 3.0 from `src/docs/completed-routes.yaml`
- [x] `src/utils/` — ApiError, ApiResponse, asyncHandler, encryption, auditLog, pagination, generateCode
- [x] `src/middlewares/` — requestId, error, validate, rateLimiter, auth, rbac, upload
- [x] `src/app.js` — Helmet, CORS, HPP, compression, rate limit, routes, Swagger, 404, error handler
- [x] `src/server.js` — graceful shutdown, BigInt JSON serializer, unhandled rejection/exception handlers
- [x] Verify `GET /health` responds
---
## 4. Modules (HMVC Pattern)
Build in this order. Each module: `*.routes.js``*.controller.js``*.service.js``*.validation.js` (+ `*.repository.js` for complex modules).
| # | Module | Status | Notes |
|---|--------|--------|-------|
| 8 | **auth** | [x] Done | login, refresh, logout, me, profile, avatar, change/forgot/reset password |
| 9 | **masters** | [x] Done | 15 sub-masters under `masters/index.js` (UOM template replicated) |
| — | **users** | [x] Done | CRUD + summary/filters/export; RBAC: `USERS` |
| — | **roles** | [x] Done | CRUD + permission assignment + matrix; RBAC: `ROLES` |
| 10 | **vendors** | [x] Done | CRUD + addresses/contacts/bank-details; encrypt bank accounts |
| 11 | **purchase-orders** | [x] Done | Header + line items; submit/approve/reject/amend/cancel; PDF export |
| 12 | **grn** | [x] Done | Receipt + PO status recalc + asset auto-creation |
| 13 | **assets** | [x] Done | CRUD + transfer + AMC/service visits/insurance + expiry alerts |
| 14 | **settings** | [x] Done | Company profile + SMTP email settings; RBAC: `SETTINGS` |
Current progress:
- [x] `auth` module (`login`, `refresh`, `logout`)
- [x] `users` module (screen APIs: summary, filters, export, CRUD)
- [x] `roles` module (cards, permission catalog, matrix, CRUD)
- [x] `masters/uom` template + 13 replicated sub-masters
- [x] Swagger docs for all completed routes (`src/docs/completed-routes.yaml`)
- [x] `vendors` module (CRUD, status, addresses, contacts, bank-details)
- [x] `purchase-orders` module (CRUD, workflow, PDF)
- [x] `grn` module (transactional receipt, PO status recalc, asset auto-creation)
- [x] `assets` module (CRUD, transfer, AMC, service visits, insurance, alerts)
- [x] `GET /auth/me` (current user + permissions for FE)
### Layer rules
| Layer | Responsibility |
|-------|----------------|
| Routes | HTTP mapping + middleware chain (`authenticate` → `authorize``validate` → controller) |
| Controller | Parse request, call service, return `ApiResponse` — no business logic, no Prisma |
| Service | Business logic, Prisma (or repository), `auditLog` on every mutation |
| Repository | Multi-table `prisma.$transaction` only (PO, GRN, Assets) |
| Validation | Joi schemas for body/query |
---
## 5. Module API List (Phase 1)
Base path: `/api/v1` · Auth: `Authorization: Bearer <accessToken>` (except public auth routes)
### System
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/health` | — | App health (root, not under v1) |
| [x] | GET | `/healthz` | — | API v1 health |
| [x] | GET | `/api-docs` | — | Swagger UI |
| [x] | GET | `/api-docs.json` | — | OpenAPI spec |
---
### Auth (`/auth`)
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | POST | `/auth/login` | Public | Returns `accessToken`, `refreshToken` |
| [x] | POST | `/auth/refresh` | Public | Rotating refresh token |
| [x] | POST | `/auth/logout` | Public | Revoke refresh token |
| [x] | POST | `/auth/forgot-password` | Public | Sends reset link via SMTP (`email_settings`) |
| [x] | POST | `/auth/reset-password` | Public | Reset password with token from email |
| [x] | GET | `/auth/me` | Authenticated | Current user + permissions (for FE) |
| [x] | PUT | `/auth/profile` | Authenticated | Update `full_name` / `email` / `mobile` |
| [x] | POST | `/auth/profile/avatar` | Authenticated | Upload avatar (`multipart`, field `avatar`) |
| [x] | POST | `/auth/change-password` | Authenticated | Change password (revokes refresh tokens) |
**DB patch:** `scripts/patch-users-avatar.sql` — adds `users.avatar_path`.
**Env:** `FRONTEND_URL` (reset link base), `PASSWORD_RESET_EXPIRY_MINUTES` (default 60).
---
### Users (`/users`) — module: `USERS`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/users/summary` | view | Dashboard cards (total/active/inactive/locked, roles count) |
| [x] | GET | `/users/filters` | view | Role, department, status dropdowns |
| [x] | GET | `/users/export` | export | CSV export (same filters as list) |
| [x] | GET | `/users` | view | Paginated list (`search`, `status`, `role_id`, `department_id`) |
| [x] | GET | `/users/:id` | view | User detail |
| [x] | POST | `/users` | create | Create user with `role_ids[]` (bcrypt password, mobile encrypted) |
| [x] | PUT | `/users/:id` | edit | Update user; optional `role_ids[]` replaces assigned roles |
| [x] | DELETE | `/users/:id` | delete | Soft delete |
---
### Roles (`/roles`) — module: `ROLES`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/roles/permissions` | view | Permission catalog (modules × actions) |
| [x] | GET | `/roles/cards` | view | Role cards (name, description, user/permission counts) |
| [x] | GET | `/roles` | view | Paginated role list |
| [x] | GET | `/roles/:id` | view | Role detail with permissions |
| [x] | POST | `/roles` | create | Create role with optional initial permissions (`permission_ids[]` and/or `view_modules[]`) |
| [x] | PUT | `/roles/:id` | edit | Update role |
| [x] | DELETE | `/roles/:id` | delete | Soft delete (blocked if users assigned) |
| [x] | PUT | `/roles/:id/permissions` | edit | Assign by `permission_ids[]` (replaces all) |
| [x] | GET | `/roles/:id/permission-matrix` | view | Matrix UI (view/create/edit/delete/approve/export) |
| [x] | PUT | `/roles/:id/permission-matrix` | edit | Save matrix checkboxes |
---
### Masters (`/masters/*`) — module: `MASTERS`
Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DELETE /:id` with RBAC `view|create|edit|delete`.
| Status | Sub-master | Base path |
|--------|------------|-----------|
| [x] | UOM | `/masters/uom` |
| [x] | Item Categories | `/masters/item-categories` | Optional asset defaults: `code_prefix`, `default_useful_life_years`, `default_depreciation_method` |
| [x] | Item Subcategories | `/masters/item-subcategories` | Filter: `item_category_id` — shared by items + assets |
| [x] | Items | `/masters/items` | `item_code` auto via `ITEM` document series; HSN via `hsn_code_id` |
| [x] | Brands | `/masters/brands` |
| [x] | GST Rates | `/masters/gst-rates` |
| [x] | HSN Codes | `/masters/hsn-codes` | Search by `code` or `description` |
| [x] | Payment Terms | `/masters/payment-terms` |
| [x] | Delivery Terms | `/masters/delivery-terms` |
| [~] | Asset Categories | removed — use Item Categories |
| [~] | Asset Subcategories | removed — use Item Subcategories |
| [x] | Departments | `/masters/departments` |
| [x] | Designations | `/masters/designations` |
| [x] | Locations | `/masters/locations` | Unified plants + warehouses (`type`: `plant` \| `warehouse`) |
| [x] | Plants | `/masters/plants` | Alias — plant locations only |
| [x] | Warehouses | `/masters/warehouses` | Alias — warehouse locations only |
| [x] | Document Series | `/masters/document-series` |
**Masters total:** 13 modules × 5 endpoints (+ plants/warehouses aliases)
---
### Vendors (`/vendors`) — module: `VENDOR`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/vendors` | view | List (`search`, `status`, `vendor_type`) |
| [x] | GET | `/vendors/gst-treatments` | view | GST treatment dropdown options |
| [x] | GET | `/vendors/source-of-supply` | view | Indian states/UTs dropdown options |
| [x] | POST | `/vendors` | create | Auto `vendor_code` via VENDOR series |
| [x] | GET | `/vendors/:id` | view | Detail + addresses, contacts, bank details, item mappings |
| [x] | PUT | `/vendors/:id` | edit | Update vendor |
| [x] | PATCH | `/vendors/:id/status` | edit | `active` / `inactive` / `blacklisted` |
| [x] | DELETE | `/vendors/:id` | delete | Soft delete |
| [x] | GET | `/vendors/:vendorId/addresses` | view | List addresses |
| [x] | POST | `/vendors/:vendorId/addresses` | create | Add address |
| [x] | GET | `/vendors/:vendorId/addresses/:addressId` | view | Get address |
| [x] | PUT | `/vendors/:vendorId/addresses/:addressId` | edit | Update address |
| [x] | DELETE | `/vendors/:vendorId/addresses/:addressId` | delete | Deactivate address |
| [x] | GET | `/vendors/:vendorId/contacts` | view | List contacts |
| [x] | POST | `/vendors/:vendorId/contacts` | create | Add contact |
| [x] | GET | `/vendors/:vendorId/contacts/:contactId` | view | Get contact |
| [x] | PUT | `/vendors/:vendorId/contacts/:contactId` | edit | Update contact |
| [x] | DELETE | `/vendors/:vendorId/contacts/:contactId` | delete | Deactivate contact |
| [x] | GET | `/vendors/:vendorId/bank-details` | view | List bank details (decrypted) |
| [x] | POST | `/vendors/:vendorId/bank-details` | create | Add bank detail (encrypted) |
| [x] | GET | `/vendors/:vendorId/bank-details/:bankDetailId` | view | Get bank detail |
| [x] | PUT | `/vendors/:vendorId/bank-details/:bankDetailId` | edit | Update bank detail |
| [x] | DELETE | `/vendors/:vendorId/bank-details/:bankDetailId` | delete | Deactivate bank detail |
| [x] | GET | `/vendors/:vendorId/items` | view | List vendoritem mappings |
| [x] | POST | `/vendors/:vendorId/items` | create | Map item to vendor |
| [x] | GET | `/vendors/:vendorId/items/:mappingId` | view | Get item mapping |
| [x] | PUT | `/vendors/:vendorId/items/:mappingId` | edit | Update rate / preferred flag |
| [x] | DELETE | `/vendors/:vendorId/items/:mappingId` | delete | Deactivate item mapping |
---
### Purchase Orders (`/purchase-orders`) — module: `PURCHASE_ORDER`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/purchase-orders` | view | List POs |
| [x] | GET | `/purchase-orders/:id` | view | PO detail + line items |
| [x] | POST | `/purchase-orders` | create | Create PO |
| [x] | PUT | `/purchase-orders/:id` | edit | Update PO |
| [x] | DELETE | `/purchase-orders/:id` | delete | Soft delete |
| [x] | POST | `/purchase-orders/:id/submit` | edit | Submit for approval |
| [x] | POST | `/purchase-orders/:id/approve` | approve | Approve PO |
| [x] | POST | `/purchase-orders/:id/reject` | approve | Reject PO |
| [x] | POST | `/purchase-orders/:id/amend` | edit | Amend PO |
| [x] | POST | `/purchase-orders/:id/cancel` | edit | Cancel PO |
| [x] | GET | `/purchase-orders/:id/pdf` | view | PDF export |
| [x] | GET | `/purchase-orders/:poId/attachments` | view | List PO attachments |
| [x] | POST | `/purchase-orders/:poId/attachments` | edit | Upload file (multipart `file`) |
| [x] | GET | `/purchase-orders/:poId/attachments/:attachmentId` | view | Attachment metadata |
| [x] | GET | `/purchase-orders/:poId/attachments/:attachmentId/download` | view | Download file (authenticated) |
| [x] | DELETE | `/purchase-orders/:poId/attachments/:attachmentId` | delete | Delete attachment + file |
**Totals:** `taxable_amount = sub_total + freight + other discount`; GST (`tax_total` / CGST+SGST / IGST) is calculated on `taxable_amount` (not on `sub_total`). Column persisted via `scripts/patch-po-taxable-amount.sql`.
---
### GRN (`/grn`) — module: `GRN`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/grn` | view | List GRNs |
| [x] | GET | `/grn/:id` | view | GRN detail + line items |
| [x] | POST | `/grn` | create | Create GRN (transactional) |
| [x] | PUT | `/grn/:id` | edit | Update GRN |
| [x] | POST | `/grn/:id/cancel` | edit | Cancel GRN |
| [x] | GET | `/grn/:id/pdf` | view | PDF export |
| [x] | GET | `/grn/:grnId/attachments` | view | List GRN attachments |
| [x] | POST | `/grn/:grnId/attachments` | edit | Upload file (multipart `file`) |
| [x] | GET | `/grn/:grnId/attachments/:attachmentId` | view | Attachment metadata |
| [x] | GET | `/grn/:grnId/attachments/:attachmentId/download` | view | Download file (authenticated) |
| [x] | DELETE | `/grn/:grnId/attachments/:attachmentId` | delete | Delete attachment + file |
---
### Assets (`/assets`) — module: `ASSET`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/assets` | view | List assets |
| [x] | GET | `/assets/:id` | view | Asset detail |
| [x] | POST | `/assets` | create | Create asset |
| [x] | PUT | `/assets/:id` | edit | Update asset |
| [x] | DELETE | `/assets/:id` | delete | Soft delete |
| [x] | POST | `/assets/:id/transfer` | edit | Transfer asset |
| [x] | GET | `/assets/:id/transfer-history` | view | Transfer history |
| [x] | GET | `/assets/:assetId/attachments` | view | List asset attachments |
| [x] | POST | `/assets/:assetId/attachments` | edit | Upload file (multipart `file`; optional `attachment_type`, AMC/visit/insurance link) |
| [x] | GET | `/assets/:assetId/attachments/:attachmentId` | view | Attachment metadata |
| [x] | GET | `/assets/:assetId/attachments/:attachmentId/download` | view | Download file (authenticated) |
| [x] | DELETE | `/assets/:assetId/attachments/:attachmentId` | delete | Delete attachment + file |
**AMC contracts** (`/assets/:id/amc`)
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/assets/:id/amc` | view | List AMC contracts |
| [x] | POST | `/assets/:id/amc` | create | Add AMC contract |
| [x] | GET | `/assets/:id/amc/:contractId` | view | AMC detail |
| [x] | PUT | `/assets/:id/amc/:contractId` | edit | Update AMC |
| [x] | PATCH | `/assets/:id/amc/:contractId/renew` | edit | Renew AMC (deactivate old, create new) |
**Service visits** (`/assets/:id/service-visits`)
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/assets/:id/service-visits` | view | List visits |
| [x] | POST | `/assets/:id/service-visits` | create | Log visit |
| [x] | GET | `/assets/:id/service-visits/:visitId` | view | Visit detail |
| [x] | PUT | `/assets/:id/service-visits/:visitId` | edit | Update visit |
| [x] | PATCH | `/assets/:id/service-visits/:visitId/status` | edit | Update visit status |
**Insurance** (`/assets/:id/insurance`)
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/assets/:id/insurance` | view | List policies |
| [x] | POST | `/assets/:id/insurance` | create | Add policy |
| [x] | GET | `/assets/:id/insurance/:policyId` | view | Policy detail |
| [x] | PUT | `/assets/:id/insurance/:policyId` | edit | Update policy |
| [x] | PATCH | `/assets/:id/insurance/:policyId/renew` | edit | Renew policy |
**Alerts** (cross-asset)
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/assets/alerts/expiry` | view | AMC/Insurance/Warranty expiry (`?days=30/60/90&type=`) |
| [x] | GET | `/assets/alerts/service` | view | Overdue/upcoming service (`?status=OVERDUE`) |
---
### Settings (`/settings`) — module: `SETTINGS`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/settings/company` | view | Company profile (org name, GSTIN, contact, address, logo/favicon URLs) |
| [x] | PUT | `/settings/company` | edit | Update company profile |
| [x] | POST | `/settings/company/logo` | edit | Upload logo (`multipart/form-data`, field `logo`) |
| [x] | POST | `/settings/company/favicon` | edit | Upload favicon (`multipart/form-data`, field `favicon`) |
| [x] | GET | `/settings/email` | view | SMTP settings (`has_smtp_password` flag; password never returned) |
| [x] | PUT | `/settings/email` | edit | Update SMTP settings (password encrypted at rest) |
**DB patch:** `scripts/patch-company-email-settings.sql` — creates `company` + `email_settings` singleton tables and `SETTINGS` module permissions for Super Admin.
**DB patch:** `scripts/patch-company-gstin.sql` — adds `company.gstin` for PDF document headers.
**DB patch:** `scripts/patch-company-favicon.sql` — adds `company.favicon_path`.
---
### Audit Logs (`/audit-logs`) — module: `AUDIT_LOGS`
| Status | Method | Endpoint | RBAC | Notes |
|--------|--------|----------|------|-------|
| [x] | GET | `/audit-logs/filters` | view | Distinct `table_names`, `actions`, `performers` for FE dropdowns |
| [x] | GET | `/audit-logs` | view | Filtered list; **empty by default** until at least one filter is applied |
| [x] | GET | `/audit-logs/:id` | view | Full detail with `old_value` / `new_value` JSON |
| [x] | GET | `/audit-logs/export` | export | CSV export (filters required) |
**List filters:** `table_name`, `record_id`, `action`, `performed_by`, `request_id`, `date_from`, `date_to`, `search` (+ `page`, `limit`).
**DB patch:** `scripts/patch-audit-logs-module.sql``AUDIT_LOGS` module with `view` + `export` for Super Admin.
---
**DB patch:** run `scripts/patch-assets-amc-insurance.sql` then `scripts/patch-assets-views.sql` on deployed DB.
---
### API progress summary
| Module | Endpoints done | Endpoints total | Status |
|--------|----------------|-----------------|--------|
| System | 4 | 4 | [x] Done |
| Auth | 9 | 9 | [x] Done |
| Users | 8 | 8 | [x] Done |
| Roles | 10 | 10 | [x] Done |
| Masters | 70 | 70 | [x] Done |
| Vendors | 27 | 27 | [x] Done |
| Purchase Orders | 11 | 11 | [x] Done |
| GRN | 11 | 11 | [x] Done |
| Assets | 24 | 24 | [x] Done |
| Settings | 5 | 5 | [x] Done |
| Audit Logs | 4 | 4 | [x] Done |
| **Total** | **172** | **172** | **[x] Phase 1 APIs + Asset extensions** |
---
## 6. Security Tasks
- [x] JWT access tokens (15m) in `Authorization: Bearer` header
- [x] Refresh tokens: opaque, SHA-256 hashed in DB, rotated on each refresh
- [x] bcrypt password hashing (`BCRYPT_SALT_ROUNDS=12`)
- [x] Account lockout after `MAX_LOGIN_ATTEMPTS` failures
- [x] `authenticate` + `authorize(module, action)` on every protected route
- [x] AES-256-GCM field encryption + HMAC blind index for searchable PII
- [x] Rate limiting: global on `/api`, stricter on `/auth/login` and `/auth/forgot-password`
- [x] Helmet, CORS (no `*` in production), HPP, compression
- [x] File uploads: MIME allow-list, random filenames, RBAC-protected download (not direct web serve)
- [x] `auditLog()` on every create/update/status-change/approve/reject (completed modules)
- [x] `X-Request-Id` on every request for log correlation
### RBAC permission actions
`view`, `create`, `edit`, `delete`, `approve`, `export`
---
## 7. API Conventions
- Versioning: `/api/v1/...`
- Pagination: `?page=1&limit=20` (cap limit at 100); response `meta: { page, limit, total }`
- Filtering: `?status=...&search=...` per module
- Sorting: `?sort=-created_at` (`-` = descending)
- IDs: `BigInt` serialized as strings in JSON
- Dates: ISO 8601 UTC
- Soft delete: set `deleted_at`, never hard-delete transactional records
- Response envelope: `{ success, message, data?, meta?, errors? }`
---
## 8. Testing
- [ ] Jest + Supertest against `app.js` (no live server)
- [ ] Separate test DB via `.env.test` + `prisma migrate deploy`
- [ ] Mirror structure: `tests/modules/<module>/<module>.test.js`
### Minimum Phase 1 coverage
- [ ] Auth: login, refresh, lockout
- [ ] RBAC: allowed vs forbidden
- [ ] Vendor: CRUD + status transitions
- [x] PO: status lifecycle
- [x] GRN: partial receipt → PO status recalculation
---
## 9. Documentation & DevOps
- [x] OpenAPI spec for completed routes (`src/docs/completed-routes.yaml` + `/api-docs`)
- [ ] Add `@swagger` JSDoc blocks to route files (optional; YAML spec in use)
- [ ] `Dockerfile` (Node 20 Alpine)
- [ ] `docker-compose.yml` (API + Postgres 15)
- [ ] Verify `docker-compose up` boots API + Postgres
- [ ] Run `npm audit` in CI
---
## 10. Future Development Guidelines
When adding a new module or endpoint:
1. Add Prisma model with common columns + soft delete
2. Add module/permissions to seed (if new domain)
3. Create the four-file HMVC module (five if multi-table transactions)
4. Register routes in `src/routes/v1/index.js`
5. Apply middleware chain: `authenticate``authorize(MODULE, action)``validate(schema)` → controller
6. Call `auditLog()` in service for all mutations
7. Use `nextDocumentNumber()` for auto-generated codes
8. Encrypt PII at service layer with blind index for search
9. Add Swagger JSDoc + Jest tests
10. Breaking API changes → new version (`/api/v2`)
### Complex transaction modules
Use `*.repository.js` with `prisma.$transaction` when a single operation touches multiple tables (e.g. PO header + items, GRN + PO qty update + asset creation).
### Masters replication
Copy the UOM module pattern for new masters: simple name/code fields, RBAC code `MASTERS`, no document numbering.