gwm : sub categort joining
This commit is contained in:
parent
22d25bf2f5
commit
6e06006687
1307
docs/MODULE_DATA_FLOW.html
Normal file
1307
docs/MODULE_DATA_FLOW.html
Normal file
File diff suppressed because it is too large
Load Diff
648
docs/MODULE_DATA_FLOW.md
Normal file
648
docs/MODULE_DATA_FLOW.md
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
# ERP Backend — Module & Data Flow Diagram
|
||||||
|
|
||||||
|
> **Stack:** Express · PostgreSQL · Prisma · JWT + RBAC
|
||||||
|
> **Base API:** `/api/v1`
|
||||||
|
> **Phase 1 modules:** Auth, Users, Roles, Masters (14), Vendors, Purchase Orders, GRN, Assets (+ AMC / Insurance / Service / Alerts / Depreciation), Settings
|
||||||
|
|
||||||
|
Reference: [BACKEND_TASKS.md](../BACKEND_TASKS.md) · [BACKEND_SETUP.md](../BACKEND_SETUP.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. High-level system architecture
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
START([Setup Masters + Vendor]) --> PO_CREATE
|
||||||
|
|
||||||
|
PO_CREATE["POST /purchase-orders<br/>status: DRAFT"] --> PO_EDIT["PUT /purchase-orders/:id<br/>add line items"]
|
||||||
|
PO_EDIT --> PO_SUBMIT["POST .../submit<br/>→ 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<br/>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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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<br/>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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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,<br/>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 (`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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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,<br/>annual_premium, start/end dates,<br/>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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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<br/>optional vendor_id ref"]
|
||||||
|
VAL_SV --> SV_ROW[(asset_service_visits)]
|
||||||
|
|
||||||
|
SV_ROW --> FIELDS["visit_type, visit_date, complaint details,<br/>engineer, work_done, parts_replaced,<br/>next_service_date, downtime_hours,<br/>service_cost, is_under_amc,<br/>asset_condition_after"]
|
||||||
|
|
||||||
|
SV_ROW --> SV_UPDATE["PUT .../service-visits/:visitId"]
|
||||||
|
SV_ROW --> SV_STATUS["PATCH .../status<br/>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_id` optional but validated against the asset
|
||||||
|
- `is_under_amc` flag for cost tracking
|
||||||
|
- `next_service_date` drives **service alerts**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Depreciation flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph Inputs
|
||||||
|
CAT[asset_categories defaults]
|
||||||
|
FORM[User form fields]
|
||||||
|
end
|
||||||
|
|
||||||
|
CAT --> |default_useful_life_years<br/>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<br/>accumulated_depreciation<br/>book_value<br/>years_elapsed<br/>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.depreciation` object
|
||||||
|
- **Method dropdown** → `GET /assets/depreciation-methods`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Alerts flow (cross-asset dashboards)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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<br/>?days=30&type=AMC|INSURANCE|WARRANTY"]
|
||||||
|
V2 --> API2["GET /assets/alerts/service<br/>?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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewing diagrams
|
||||||
|
|
||||||
|
- **GitHub** — renders Mermaid natively in markdown
|
||||||
|
- **VS Code** — install a Mermaid preview extension
|
||||||
|
- **Online** — paste into [mermaid.live](https://mermaid.live)
|
||||||
@ -1,5 +1,6 @@
|
|||||||
const prisma = require('../../../config/prisma');
|
const prisma = require('../../../config/prisma');
|
||||||
const ApiError = require('../../../utils/ApiError');
|
const ApiError = require('../../../utils/ApiError');
|
||||||
|
const { getPagination } = require('../../../utils/pagination');
|
||||||
const { buildMasterService } = require('../_shared/master.factory');
|
const { buildMasterService } = require('../_shared/master.factory');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -51,6 +52,21 @@ const config = {
|
|||||||
|
|
||||||
const service = buildMasterService(config);
|
const service = buildMasterService(config);
|
||||||
|
|
||||||
|
const categoryInclude = {
|
||||||
|
asset_categories: { select: { id: true, code: true, name: true } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeSubcategory = (row) => {
|
||||||
|
if (!row) return null;
|
||||||
|
const { asset_categories, ...rest } = row;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
asset_category: asset_categories || null,
|
||||||
|
asset_category_name: asset_categories?.name || null,
|
||||||
|
asset_categories: undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const assertAssetCategory = async (assetCategoryId) => {
|
const assertAssetCategory = async (assetCategoryId) => {
|
||||||
const category = await prisma.asset_categories.findFirst({
|
const category = await prisma.asset_categories.findFirst({
|
||||||
where: { id: BigInt(assetCategoryId), deleted_at: null },
|
where: { id: BigInt(assetCategoryId), deleted_at: null },
|
||||||
@ -60,22 +76,83 @@ const assertAssetCategory = async (assetCategoryId) => {
|
|||||||
return category;
|
return category;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const withCategory = async (row) => {
|
||||||
|
if (!row) return null;
|
||||||
|
const full = await prisma.asset_subcategories.findFirst({
|
||||||
|
where: { id: row.id },
|
||||||
|
include: categoryInclude,
|
||||||
|
});
|
||||||
|
return sanitizeSubcategory(full || row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listAssetSubcategories = async (query) => {
|
||||||
|
const { page, limit, skip } = getPagination(query);
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
deleted_at: null,
|
||||||
|
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
|
||||||
|
...(query.asset_category_id
|
||||||
|
? { asset_category_id: BigInt(query.asset_category_id) }
|
||||||
|
: {}),
|
||||||
|
...(query.search
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{ code: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
{ name: { contains: query.search, mode: 'insensitive' } },
|
||||||
|
{
|
||||||
|
asset_categories: {
|
||||||
|
name: { contains: query.search, mode: 'insensitive' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [rows, total] = await Promise.all([
|
||||||
|
prisma.asset_subcategories.findMany({
|
||||||
|
where,
|
||||||
|
include: categoryInclude,
|
||||||
|
orderBy: { created_at: 'desc' },
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
prisma.asset_subcategories.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: rows.map(sanitizeSubcategory),
|
||||||
|
meta: { page, limit, total },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAssetSubcategoriesById = async (id) => {
|
||||||
|
const row = await prisma.asset_subcategories.findFirst({
|
||||||
|
where: { id: BigInt(id), deleted_at: null },
|
||||||
|
include: categoryInclude,
|
||||||
|
});
|
||||||
|
if (!row) throw new ApiError(404, 'asset_subcategories not found');
|
||||||
|
return sanitizeSubcategory(row);
|
||||||
|
};
|
||||||
|
|
||||||
const createAssetSubcategories = async (payload, userId, requestId) => {
|
const createAssetSubcategories = async (payload, userId, requestId) => {
|
||||||
await assertAssetCategory(payload.asset_category_id);
|
await assertAssetCategory(payload.asset_category_id);
|
||||||
return service.createOne(payload, userId, requestId);
|
const created = await service.createOne(payload, userId, requestId);
|
||||||
|
return withCategory(created);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAssetSubcategories = async (id, payload, userId, requestId) => {
|
const updateAssetSubcategories = async (id, payload, userId, requestId) => {
|
||||||
if (payload.asset_category_id !== undefined) {
|
if (payload.asset_category_id !== undefined) {
|
||||||
await assertAssetCategory(payload.asset_category_id);
|
await assertAssetCategory(payload.asset_category_id);
|
||||||
}
|
}
|
||||||
return service.updateOne(id, payload, userId, requestId);
|
const updated = await service.updateOne(id, payload, userId, requestId);
|
||||||
|
return withCategory(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createAssetSubcategories,
|
createAssetSubcategories,
|
||||||
listAssetSubcategories: service.list,
|
listAssetSubcategories,
|
||||||
getAssetSubcategoriesById: service.getOne,
|
getAssetSubcategoriesById,
|
||||||
updateAssetSubcategories,
|
updateAssetSubcategories,
|
||||||
deleteAssetSubcategories: service.removeOne,
|
deleteAssetSubcategories: service.removeOne,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user