ERP Backend — Module & Data Flow Diagram
+ + + +1. High-level system architecture
+
+
+
+ +flowchart TB + subgraph Client["Frontend / API Client"] + FE[React App / Postman] + end + + subgraph Gateway["Express App (src/app.js)"] + HELMET[Helmet + CORS + HPP] + RATE[Rate Limiter /api] + REQID[Request ID] + ROUTES["/api/v1 Routes"] + SWAGGER[Swagger /api-docs] + ERR[Error Middleware] + end + + subgraph Middleware["Per-route chain"] + AUTH[authenticate JWT] + RBAC[authorize module + action] + VAL[validate Joi schema] + CTRL[Controller] + end + + subgraph Services["Business Layer"] + SVC["*.service.js"] + REPO["*.repository.js PO/GRN/Assets"] + end + + subgraph Data["PostgreSQL"] + PRISMA[(Prisma ORM)] + VIEWS[(SQL Views v_asset_*)] + AUDIT[(audit_logs)] + end + + FE --> HELMET --> RATE --> REQID --> ROUTES + ROUTES --> AUTH --> RBAC --> VAL --> CTRL --> SVC + SVC --> PRISMA + SVC --> REPO --> PRISMA + SVC --> AUDIT + SVC --> VIEWS + CTRL --> ERR ++
2. Every API module map
+
+
+
+ +flowchart LR + subgraph System + HEALTH[/health /healthz] + DOCS[/api-docs] + end + + subgraph Security + AUTH_MOD["/auth login|refresh|logout|me"] + USERS["/users CRUD + export"] + ROLES["/roles CRUD + permissions matrix"] + end + + subgraph Masters["/masters/* (14 sub-masters)"] + M1[uom] + M2[item-categories / subcategories / items] + M3[brands / gst-rates] + M4[payment-terms / delivery-terms] + M5[asset-categories / subcategories] + M6[departments / designations] + M7[locations / plants / warehouses] + M8[document-series] + end + + subgraph Procurement + VEND["/vendors + addresses|contacts|bank|items"] + PO["/purchase-orders workflow + PDF"] + GRN["/grn receipt + cancel + PDF"] + end + + subgraph Assets["/assets"] + A_CORE[CRUD + transfer] + A_AMC[AMC contracts + renew] + A_SVC[service visits + status] + A_INS[insurance + renew] + A_DEPR[depreciation preview] + A_ALERT[expiry + service alerts] + end + + subgraph Config + SET["/settings company + email SMTP"] + end + + System --> Security --> Masters --> Procurement --> Assets --> Config ++
3. Standard request data flow (all protected routes)
+
+
+
+sequenceDiagram
+ participant FE as Frontend
+ participant API as Express Route
+ participant AUTH as authenticate
+ participant RBAC as authorize
+ participant VAL as validate
+ participant SVC as Service
+ participant DB as PostgreSQL
+ participant AUD as audit_logs
+
+ FE->>API: HTTP + Bearer JWT
+ API->>AUTH: Verify token, load user + roles
+ AUTH->>RBAC: Check module permission (view/create/edit/delete/approve/export)
+ RBAC->>VAL: Joi validate body/query
+ VAL->>SVC: Business logic
+ SVC->>DB: Prisma read/write (deleted_at: null)
+ SVC->>AUD: auditLog on mutations
+ SVC-->>FE: ApiResponse { success, message, data, meta }
+
+ RBAC modules: USERS, ROLES, MASTERS, VENDOR, PURCHASE_ORDER, GRN, ASSET, SETTINGS
4. Auth & session flow
+
+
+ +stateDiagram-v2 + [*] --> Login: POST /auth/login + Login --> Active: Valid credentials + Login --> Locked: MAX_LOGIN_ATTEMPTS exceeded + Login --> RateLimited: Too many failed logins (IP) + Active --> TokenIssued: accessToken 15m + refreshToken + TokenIssued --> APIAccess: Bearer on /api/v1/* + APIAccess --> Refresh: access expired + Refresh --> TokenIssued: POST /auth/refresh (rotate hash in DB) + APIAccess --> Logout: POST /auth/logout (revoke refresh) + Locked --> Login: After LOCKOUT_DURATION_MINUTES ++
| Layer | Storage | Reset |
|---|---|---|
| Global API rate limit | In-memory per IP | Restart server or wait 15 min |
| Login rate limit | In-memory per IP | Restart or wait 15 min |
| Account lockout | users.locked_until | Wait 30 min or DB reset |
5. Master data dependency graph
+Masters must exist before transactional modules use them.
+
+
+
+ +flowchart TD + DS[document_series] --> VCODE[vendor_code / po_number / grn_number / asset_code] + LOC[locations plants + warehouses] --> PO + LOC --> GRN + LOC --> ASSET + + DEPT[departments] --> USERS + DEPT --> ASSET + DES[designations] --> USERS + + UOM[uom] --> ITEMS + IC[item_categories] --> ISUB[item_subcategories] --> ITEMS + GST[gst_rates] --> ITEMS + BRANDS[brands] --> ITEMS + BRANDS --> PO + + PT[payment_terms] --> VEND + PT --> PO + DT[delivery_terms] --> PO + + ACAT[asset_categories] --> ASUB[asset_subcategories] + ACAT --> ASSET + ACAT --> GRN_AUTO[GRN auto-asset creation] + ASUB --> ASSET + ASUB --> GRN_AUTO + + ITEMS --> PO_ITEMS + ITEMS --> GRN_ITEMS + VEND --> PO + VEND --> GRN + VEND --> AMC + VEND --> SVC_VISIT ++
6. Procurement → Asset end-to-end flow (core business chain)
+
+
+
+ +flowchart TD + START([Setup Masters + Vendor]) --> PO_CREATE + + PO_CREATE["POST /purchase-orders+
status: DRAFT"] --> PO_EDIT["PUT /purchase-orders/:id
add line items"] + PO_EDIT --> PO_SUBMIT["POST .../submit
→ PENDING_APPROVAL"] + PO_SUBMIT --> PO_APPROVE{"Approve?"} + PO_APPROVE -->|Yes| PO_APPROVED["status: APPROVED"] + PO_APPROVE -->|No| PO_REJECTED["status: REJECTED → edit & resubmit"] + PO_REJECTED --> PO_EDIT + + PO_APPROVED --> GRN_CREATE["POST /grn
receive against PO lines"] + + subgraph GRN_TXN["GRN Transaction (repository)"] + G1[Create grn POSTED] + G2[Create grn_items per line] + G3[Increment PO item received_qty] + G4{is_asset_item?} + G5[Auto-create assets 1 per accepted qty] + G6[Recalculate PO status] + G1 --> G2 --> G3 --> G4 + G4 -->|Yes| G5 --> G6 + G4 -->|No| G6 + end + + GRN_CREATE --> GRN_TXN + G6 --> PO_STATUS{Receipt status} + PO_STATUS -->|partial| PARTIAL[PARTIALLY_RECEIVED] + PO_STATUS -->|full| FULL[FULLY_RECEIVED] + + G5 --> MANUAL_ASSET["POST /assets manual create also allowed"] + PARTIAL --> GRN_CREATE + FULL --> ASSET_OPS[Asset lifecycle ops] + + GRN_CANCEL["POST /grn/:id/cancel"] --> REV1[Decrement received_qty] + REV1 --> REV2[Soft-delete linked assets] + REV2 --> REV3[GRN status CANCELLED] +
PO status lifecycle
+
+
+
+ +stateDiagram-v2 + [*] --> DRAFT + DRAFT --> PENDING_APPROVAL: submit + REJECTED --> PENDING_APPROVAL: resubmit + PENDING_APPROVAL --> APPROVED: approve + PENDING_APPROVAL --> REJECTED: reject + APPROVED --> PARTIALLY_RECEIVED: GRN partial + APPROVED --> FULLY_RECEIVED: GRN full + PARTIALLY_RECEIVED --> FULLY_RECEIVED: more GRN + APPROVED --> CLOSED: amend closes old PO + DRAFT --> CANCELLED: cancel + APPROVED --> CANCELLED: cancel if no receipts ++
GRN auto-asset creation (when items.is_asset_item = true)
+ | Field on Asset | Source |
|---|---|
asset_code | document_series via ASSET_{category.code} |
asset_name | Item name (+ #N if qty > 1) |
asset_category_id / asset_subcategory_id | From GRN line payload |
plant_id | From PO |
warehouse_id, vendor_id, po_id, grn_id, grn_item_id | From GRN |
purchase_date | grn.grn_date |
purchase_cost | grn_items.rate |
useful_life_years, depreciation_method | From asset_categories defaults |
condition | NEW |
status | IN_USE |
7. Asset module — full lifecycle
+
+
+
+ +flowchart TB + subgraph Create["Asset Creation Paths"] + P1[Manual POST /assets] + P2[Auto from GRN receipt] + end + + subgraph Validate["normalizeAssetPayload validations"] + V1[asset_category + subcategory match] + V2[plant / dept / warehouse / user refs] + V3[vendor / PO / GRN / grn_item refs] + V4[disposal_date required if DISPOSED/SCRAPPED] + V5[depreciation_rate required if method=OTHER] + V6[resolve rate from category defaults] + end + + Create --> Validate --> SAVE[(assets table)] + SAVE --> CODE[asset_code from document_series] + + SAVE --> READ["GET /assets/:id+
includes depreciation summary"] + + SAVE --> TRANSFER["POST /assets/:id/transfer"] + TRANSFER --> TH[(asset_transfers)] + TRANSFER --> UPDATE_LOC[Update plant/dept/user/warehouse on asset] + + SAVE --> UPDATE["PUT /assets/:id"] + SAVE --> DELETE["DELETE /assets/:id soft delete"] + + UPDATE --> DISPOSE{status DISPOSED/SCRAPPED?} + DISPOSE -->|Yes| NEED_DATE[disposal_date required] +
Asset status & condition enums
+| Field | Values |
|---|---|
status | IN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED |
condition | NEW, GOOD, FAIR, POOR |
8. AMC contracts flow + renewal concept
+
+
+
+flowchart TD
+ A[(assets)] --> AMC_LIST["GET /assets/:id/amc"]
+ A --> AMC_CREATE["POST /assets/:id/amc"]
+
+ AMC_CREATE --> CHECK1{is_active=true?}
+ CHECK1 -->|Yes| DEACT[Deactivate other active AMC for same asset]
+ CHECK1 --> AMC_ROW[(asset_amc_contracts)]
+ DEACT --> AMC_ROW
+
+ AMC_ROW --> FIELDS["vendor_id, contract_type, start/end dates,
renewal_date, annual_cost, visits_per_year, etc."]
+
+ AMC_ROW --> AMC_UPDATE["PUT /assets/:id/amc/:contractId"]
+ AMC_ROW --> AMC_RENEW["PATCH .../renew"]
+
+ subgraph RENEW_FLOW["Renewal Flow (AMC)"]
+ R1[Load existing contract]
+ R2[Set old contract is_active=false]
+ R3[Create NEW contract row with new dates]
+ R4[New contract is_active=true]
+ R5[auditLog action=RENEW]
+ R1 --> R2 --> R3 --> R4 --> R5
+ end
+
+ AMC_RENEW --> RENEW_FLOW
+
+ AMC_ROW --> SVC_LINK["Service visits can link amc_contract_id"]
+
+
+ Key rule: Only one active AMC per asset (
+ Renewal ≠ update dates on same row — renewal creates a new contract record and deactivates the old one (history preserved). +
+
+ deactivateOtherActive).+ Renewal ≠ update dates on same row — renewal creates a new contract record and deactivates the old one (history preserved). +
9. Insurance policies flow + renewal concept
+
+
+
+flowchart TD
+ A[(assets)] --> INS_LIST["GET /assets/:id/insurance"]
+ A --> INS_CREATE["POST /assets/:id/insurance"]
+
+ INS_CREATE --> CHECK{is_active=true?}
+ CHECK -->|Yes| DEACT[Deactivate other active policies]
+ CHECK --> POL[(asset_insurance_policies)]
+ DEACT --> POL
+
+ POL --> FIELDS["policy_no, insurer, sum_insured,
annual_premium, start/end dates,
renewal_date, is_auto_renewal, premium_paid"]
+
+ POL --> INS_UPDATE["PUT /assets/:id/insurance/:policyId"]
+ POL --> INS_RENEW["PATCH .../renew"]
+
+ subgraph RENEW_INS["Renewal Flow (Insurance)"]
+ I1[Deactivate old policy]
+ I2[Create new policy with new dates]
+ I3[premium_paid defaults false on renew]
+ I4[auditLog action=RENEW]
+ I1 --> I2 --> I3 --> I4
+ end
+
+ INS_RENEW --> RENEW_INS
+
+ Same pattern as AMC: one active policy, renewal = new row + deactivate old.
+ +10. Service visits flow
+
+
+
+ +flowchart TD + A[(assets)] --> SV_LIST["GET /assets/:id/service-visits"] + A --> SV_CREATE["POST /assets/:id/service-visits"] + + SV_CREATE --> VAL_SV["Validate amc_contract_id belongs to asset+
optional vendor_id ref"] + VAL_SV --> SV_ROW[(asset_service_visits)] + + SV_ROW --> FIELDS["visit_type, visit_date, complaint details,
engineer, work_done, parts_replaced,
next_service_date, downtime_hours,
service_cost, is_under_amc,
asset_condition_after"] + + SV_ROW --> SV_UPDATE["PUT .../service-visits/:visitId"] + SV_ROW --> SV_STATUS["PATCH .../status
SCHEDULED|IN_PROGRESS|COMPLETED|etc."] + + SV_ROW --> ALERT_VIEW["Feeds v_asset_next_service view"] +
Visit types
+PREVENTIVE, BREAKDOWN, INSPECTION, INSTALLATION, CALIBRATION, OTHER
Link to AMC
+-
+
amc_contract_idoptional but validated against the asset
+ is_under_amcflag for cost tracking
+ next_service_datedrives service alerts
+
11. Depreciation flow
+
+
+ +flowchart TD + subgraph Inputs + CAT[asset_categories defaults] + FORM[User form fields] + end + + CAT --> |default_useful_life_years+
default_depreciation_method| CREATE + FORM --> CREATE["POST /assets or PUT /assets/:id"] + FORM --> PREVIEW["POST /assets/depreciation/calculate"] + + subgraph Methods["depreciation_method"] + SLM[SLM Straight Line] + WDV[WDV Written Down Value] + OTHER[OTHER manual rate required] + end + + PREVIEW --> CALC[calculateDepreciation] + CREATE --> STORE[(assets: method, rate, cost, salvage, life, purchase_date)] + STORE --> READ["GET /assets/:id"] + READ --> CALC + + CALC --> OUT["annual_depreciation
accumulated_depreciation
book_value
years_elapsed
resolved depreciation_rate"] + + SLM --> CALC + WDV --> CALC + OTHER --> CALC +
| Method | Rate auto-calc? | Formula concept |
|---|---|---|
| SLM | Yes (if rate omitted) | Equal yearly depreciation on original cost |
| WDV | Yes (if rate omitted) | Depreciation on reducing book value year-by-year |
| OTHER | No — user must send depreciation_rate | Custom % on cost |
FE usage:
+-
+
- Live preview →
POST /assets/depreciation/calculate(no save)
+ - Saved asset view →
GET /assets/:id→data.depreciationobject
+ - Method dropdown →
GET /assets/depreciation-methods
+
12. Alerts flow (cross-asset dashboards)
+
+
+ +flowchart TD + subgraph Sources + AMC[(asset_amc_contracts end_date)] + INS[(asset_insurance_policies policy_end_date)] + WAR[(assets warranty_expiry_date)] + SVC[(asset_service_visits next_service_date)] + end + + AMC --> V1[v_asset_expiry_alerts] + INS --> V1 + WAR --> V1 + SVC --> V2[v_asset_next_service] + + V1 --> API1["GET /assets/alerts/expiry+
?days=30&type=AMC|INSURANCE|WARRANTY"] + V2 --> API2["GET /assets/alerts/service
?status=OVERDUE|DUE_THIS_WEEK|..."] + + V1 --> LEVELS["EXPIRED / CRITICAL / WARNING / INFO"] + V2 --> SSTAT["OVERDUE / DUE_THIS_WEEK / DUE_THIS_MONTH / UPCOMING"] +
Requires DB views: run scripts/patch-assets-amc-insurance.sql + scripts/patch-assets-views.sql
13. Vendor module data flow
+
+
+
+ +flowchart LR + V[(vendors)] --> ADDR[vendor_addresses] + V --> CONT[vendor_contacts] + V --> BANK[vendor_bank_details AES encrypted] + V --> MAP[vendor_item_mapping] + + V --> PO[purchase_orders] + V --> GRN[grn] + V --> AMC[asset_amc_contracts] + V --> SVC[asset_service_visits] + + MAP --> ITEMS[items preferred vendor rates] ++
14. Users & roles permission flow
+
+
+
+ +flowchart TD + U[(users)] --> UR[user_roles] + UR --> R[(roles)] + R --> RP[role_permissions] + RP --> P[(permissions)] + P --> M[(modules)] + + U --> PO_APPROVE[PO approve/reject] + U --> ASSET_OPS[Asset CRUD/transfer] + U --> AUDIT[performed_by in audit_logs] + + LOGIN["GET /auth/me"] --> PERM[Returns user + flat permissions list for FE menu/RBAC] ++
15. Settings module
+
+
+
+ +flowchart LR + CO[(company singleton)] --> PDF[PO/GRN/Asset PDF headers] + EM[(email_settings SMTP encrypted)] --> MAIL[Future email notifications] + LOGO["POST /settings/company/logo"] --> UPLOADS[(uploads/)] ++
16. Cross-cutting concerns (every module)
+
+
+
+ +flowchart TD + M[Every mutation] --> AUDIT[auditLog table_name, record_id, action, old/new JSON] + M --> SOFT[Soft delete deleted_at = now] + M --> USER[created_by / updated_by from req.user.id] + M --> REQ[request_id from middleware] + + DOC[Document numbers] --> SERIES[document_series atomic increment] + SERIES --> VC[vendor_code] + SERIES --> PO_NUM[po_number] + SERIES --> GRN_NUM[grn_number] + SERIES --> ASSET_CODE[asset_code per category prefix] + + PII[mobile, bank accounts] --> ENC[AES-256-GCM + HMAC blind index] ++
17. Complete entity relationship (simplified)
+
+
+
+
+erDiagram
+ vendors ||--o{ purchase_orders : supplies
+ vendors ||--o{ grn : delivers
+ vendors ||--o{ asset_amc_contracts : maintains
+ vendors ||--o{ asset_service_visits : services
+
+ purchase_orders ||--o{ purchase_order_items : contains
+ purchase_orders ||--o{ grn : received_via
+ purchase_orders ||--o{ assets : sourced_from
+
+ grn ||--o{ grn_items : lines
+ grn_items ||--o{ assets : auto_created
+
+ items ||--o{ purchase_order_items : ordered
+ items ||--o{ grn_items : received
+
+ asset_categories ||--o{ asset_subcategories : has
+ asset_categories ||--o{ assets : classifies
+ asset_subcategories ||--o{ assets : sub_classifies
+
+ assets ||--o{ asset_amc_contracts : has
+ assets ||--o{ asset_insurance_policies : insured_by
+ assets ||--o{ asset_service_visits : serviced
+ assets ||--o{ asset_transfers : moved
+ assets ||--o{ asset_attachments : files
+
+ asset_amc_contracts ||--o{ asset_service_visits : covers
+
+ locations ||--o{ assets : plant_warehouse
+ departments ||--o{ assets : assigned_dept
+ users ||--o{ assets : assigned_user
+
+ users ||--o{ user_roles : has
+ roles ||--o{ role_permissions : grants
+ permissions }o--|| modules : belongs_to
+
+ 18. Asset sub-module API quick reference
+| Area | Endpoints | Notes |
|---|---|---|
| Core | GET/POST /assets, GET/PUT/DELETE /assets/:id | Includes computed depreciation on read |
| Transfer | POST /assets/:id/transfer, GET .../transfer-history | Blocked if DISPOSED/SCRAPPED |
| AMC | GET/POST /assets/:id/amc, GET/PUT .../:contractId, PATCH .../renew | One active contract |
| Service | GET/POST /assets/:id/service-visits, PUT, PATCH .../status | Links optional AMC |
| Insurance | GET/POST /assets/:id/insurance, GET/PUT, PATCH .../renew | One active policy |
| Depreciation | GET /depreciation-methods, POST /depreciation/calculate | Preview only |
| Alerts | GET /alerts/expiry, GET /alerts/service | SQL views |
19. Typical FE screen → API mapping
+| Screen | APIs used |
|---|---|
| Login | POST /auth/login, GET /auth/me |
| Master setup | /masters/* CRUD |
| Vendor management | /vendors/* |
| Create PO | Masters dropdowns + POST /purchase-orders |
| Approve PO | POST /purchase-orders/:id/approve |
| GRN receipt | POST /grn (auto assets for asset items) |
| Asset list/detail | GET /assets, GET /assets/:id |
| Asset create/edit form | Masters + POST /depreciation/calculate (preview) + POST/PUT /assets |
| AMC tab | /assets/:id/amc/* + renew |
| Insurance tab | /assets/:id/insurance/* + renew |
| Service log | /assets/:id/service-visits/* |
| Dashboard alerts | /assets/alerts/expiry, /assets/alerts/service |
| Transfer asset | POST /assets/:id/transfer |
20. Renewal concept summary (for FE)
+| Entity | What "Renew" does | Old record | New record |
|---|---|---|---|
| AMC | +PATCH /assets/:id/amc/:contractId/renew |
+ is_active = false |
+ New row, is_active = true, new dates |
+
| Insurance | +PATCH /assets/:id/insurance/:policyId/renew |
+ is_active = false |
+ New row, is_active = true, new policy period |
+
Both preserve full history — never overwrite the old contract/policy row.
+ ++ +