809 lines
26 KiB
Markdown
809 lines
26 KiB
Markdown
# 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/* (item categories shared with assets)"]
|
||
M1[uom]
|
||
M2[item-categories / subcategories / items]
|
||
M3[brands / gst-rates]
|
||
M4[payment-terms / delivery-terms]
|
||
M5[departments / designations]
|
||
M6[locations / plants / warehouses]
|
||
M7[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`, `AUDIT_LOGS`
|
||
|
||
### Audit logs (read-only)
|
||
|
||
| Endpoint | Purpose |
|
||
|---|---|
|
||
| `GET /audit-logs/filters` | Dropdown values: tables, actions, performers |
|
||
| `GET /audit-logs` | Filtered list — **returns empty until a filter is set** |
|
||
| `GET /audit-logs/:id` | Full row with `old_value` / `new_value` |
|
||
| `GET /audit-logs/export` | CSV of filtered rows |
|
||
|
||
Typical FE flow: load filters → user picks table + record (or date range) → list → click row for detail drawer.
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
ICAT[item_categories] --> ISUB[item_subcategories]
|
||
ISUB --> ITEMS[items]
|
||
ICAT --> ASSET
|
||
ICAT --> GRN_AUTO[GRN auto-asset creation]
|
||
ISUB --> ASSET
|
||
ISUB --> 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 goods receiving concept flow
|
||
|
||
End-to-end warehouse receipt: PO-backed GRN posting, optional supporting documents, PO qty update, and asset auto-creation.
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph Physical["Physical receipt at warehouse"]
|
||
P1[Vendor delivery arrives]
|
||
P2[Verify PO / invoice / LR / vehicle]
|
||
P3[Inspect qty quality batch]
|
||
end
|
||
|
||
subgraph System["System — GRN posting"]
|
||
S1["GET open PO<br/>status APPROVED / PARTIALLY_RECEIVED"]
|
||
S2["POST /grn<br/>header + line items"]
|
||
S3["GRN txn: grn POSTED"]
|
||
S4["grn_items per accepted/rejected qty"]
|
||
S5["PO received_qty += accepted_qty"]
|
||
S6{Item is_asset_item?}
|
||
S7["Auto-create assets<br/>1 per accepted unit"]
|
||
S8["Recalculate PO status"]
|
||
end
|
||
|
||
subgraph Docs["Supporting documents (after or alongside GRN)"]
|
||
D1["POST /grn/:grnId/attachments<br/>multipart file"]
|
||
D2["grn_attachments row"]
|
||
D3["File stored under uploads/grn/{grnId}/"]
|
||
D4["GET .../download — authenticated"]
|
||
end
|
||
|
||
P1 --> P2 --> P3 --> S1 --> S2 --> S3 --> S4 --> S5 --> S6
|
||
S6 -->|Yes| S7 --> S8
|
||
S6 -->|No| S8
|
||
S3 --> D1 --> D2 --> D3
|
||
D3 --> D4
|
||
|
||
S8 --> OUT1{More qty pending?}
|
||
OUT1 -->|Yes| PARTIAL[PO PARTIALLY_RECEIVED]
|
||
OUT1 -->|No| FULL[PO FULLY_RECEIVED]
|
||
|
||
CANCEL["POST /grn/:id/cancel"] --> REV1[Reverse PO received_qty]
|
||
REV1 --> REV2[Soft-delete linked assets]
|
||
REV2 --> REV3[GRN CANCELLED — attachments remain on record]
|
||
```
|
||
|
||
| Step | Actor | API / table | Notes |
|
||
|------|-------|-------------|-------|
|
||
| 1 | Store / procurement | `POST /grn` | Requires APPROVED PO with pending qty |
|
||
| 2 | System | `grn`, `grn_items` | `accepted_qty + rejected_qty = current_qty` per line |
|
||
| 3 | System | `purchase_order_items.received_qty` | Incremented in same transaction |
|
||
| 4 | System | `assets` | Created when `items.is_asset_item = true` |
|
||
| 5 | Store user | `POST /grn/:grnId/attachments` | Invoice PDF, LR copy, photos (PDF/JPEG/PNG/WebP) |
|
||
| 6 | Any authorized user | `GET .../attachments/:id/download` | RBAC-protected download — not public `/uploads` |
|
||
| 7 | GRN detail | `GET /grn/:id` | Includes `attachments[]` metadata |
|
||
|
||
**Typical attachment types:** vendor tax invoice, LR/eway bill, packing list, QC rejection photos, delivery challan.
|
||
|
||
### 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) |
|
||
| `item_category_id` / `item_subcategory_id` | From item master (optional GRN line override) |
|
||
| `location_id` | From PO `shipping_id` (plant or warehouse); same source as GRN `location_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 `item_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[item_category + subcategory match]
|
||
V2[location / dept / user refs]
|
||
V3[vendor / PO / GRN / grn_item refs]
|
||
V4[disposal_date required if DISPOSED/SCRAPPED]
|
||
V5[depreciation_rate required if method=CUSTOM]
|
||
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 location/dept/user 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[item_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]
|
||
CUSTOM[CUSTOM manual rate required]
|
||
end
|
||
|
||
PREVIEW --> CALC[calculateDepreciation]
|
||
CREATE --> STORE[(assets: method, rate, cost, salvage, life, commencement/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
|
||
CUSTOM --> CALC
|
||
```
|
||
|
||
### Input fields used in calculation
|
||
|
||
| Field | Meaning | Role in formula |
|
||
|---|---|---|
|
||
| `purchase_cost` | Asset cost / amount | Starting book value; base for rate & annual dep |
|
||
| `salvage_value` | Residual value at end of life (amount) | Floor for book value; used in rate auto-calc |
|
||
| `salvage_percentage` | Residual as % of purchase_cost (0–100) | Synced with amount; FE may send either |
|
||
| `useful_life_years` | Expected life in years | Rate auto-calc + cap on years elapsed |
|
||
| `commencement_date` | Usage / put-to-use date | **Primary** start date for years elapsed |
|
||
| `purchase_date` | Purchase date | Fallback start date if commencement is null |
|
||
| `depreciation_method` | `SLM` / `WDV` / `CUSTOM` | Which formula path to run |
|
||
| `depreciation_rate` | % per year | Optional for SLM/WDV (auto); required for CUSTOM |
|
||
| `as_of_date` | Calculate as of (preview only) | Defaults to today |
|
||
|
||
### Date → years elapsed
|
||
|
||
```
|
||
depreciation_start_date = commencement_date || purchase_date
|
||
years_elapsed = (as_of_date − depreciation_start_date) / 365.25 days
|
||
capped_years = min(years_elapsed, useful_life_years) // if life is set
|
||
```
|
||
|
||
Accumulated depreciation uses `capped_years` so it never exceeds useful life.
|
||
|
||
### Method 1 — SLM (Straight Line)
|
||
|
||
Equal depreciation every year on **original cost**, never below salvage.
|
||
|
||
**Auto rate** (when `depreciation_rate` omitted):
|
||
|
||
```
|
||
depreciable_amount = max(purchase_cost − salvage_value, 0)
|
||
depreciation_rate = (depreciable_amount / purchase_cost / useful_life_years) × 100
|
||
```
|
||
|
||
**Amounts:**
|
||
|
||
```
|
||
annual_depreciation = purchase_cost × rate / 100
|
||
accumulated_depreciation = min(annual_depreciation × capped_years, purchase_cost − salvage_value)
|
||
book_value = max(purchase_cost − accumulated_depreciation, salvage_value)
|
||
```
|
||
|
||
**Example:** cost = ₹1,00,000 · salvage = ₹10,000 · life = 10 years · start = 1 year ago
|
||
|
||
- Rate = ((100000−10000)/100000/10)×100 = **9%**
|
||
- Annual = 100000 × 9% = **₹9,000**
|
||
- Accumulated (1 yr) = **₹9,000**
|
||
- Book value = **₹91,000**
|
||
|
||
### Method 2 — WDV (Written Down Value)
|
||
|
||
Depreciation each year on the **reducing book value**. Book value never goes below salvage.
|
||
|
||
**Auto rate** (when `depreciation_rate` omitted; needs salvage > 0 and < cost):
|
||
|
||
```
|
||
depreciation_rate = (1 − (salvage_value / purchase_cost)^(1 / useful_life_years)) × 100
|
||
```
|
||
|
||
**Amounts (year-by-year):**
|
||
|
||
```
|
||
book_value = purchase_cost
|
||
for each full year in capped_years:
|
||
year_dep = book_value × rate / 100
|
||
book_value = max(book_value − year_dep, salvage_value)
|
||
if fractional year remains:
|
||
year_dep = (book_value × rate / 100) × fraction
|
||
book_value = max(book_value − year_dep, salvage_value)
|
||
|
||
accumulated_depreciation = purchase_cost − book_value
|
||
annual_depreciation = book_value × rate / 100 // next year's dep on current WDV
|
||
```
|
||
|
||
**Example:** cost = ₹1,00,000 · salvage = ₹10,000 · life = 10 years · start = 1 year ago
|
||
|
||
- Rate ≈ (1 − (10000/100000)^(1/10)) × 100 ≈ **20.57%**
|
||
- Year 1 dep ≈ 100000 × 20.57% ≈ **₹20,570**
|
||
- Book value ≈ **₹79,430**
|
||
- Accumulated ≈ **₹20,570**
|
||
- Next annual (on WDV) ≈ 79430 × 20.57% ≈ **₹16,340**
|
||
|
||
### CUSTOM method
|
||
|
||
Same accumulation style as SLM, but `depreciation_rate` is **mandatory** (no auto-calc).
|
||
|
||
| Method | Rate auto-calc? | Depreciates on | Salvage role |
|
||
|---|---|---|---|
|
||
| **SLM** | Yes (if rate omitted) | Original `purchase_cost` every year | In rate formula + book-value floor |
|
||
| **WDV** | Yes (if rate omitted) | Reducing book value each year | In rate formula + book-value floor |
|
||
| **CUSTOM** | No — send `depreciation_rate` | Original cost × rate (like SLM) | Book-value floor only |
|
||
|
||
**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`
|
||
- Prefer sending `commencement_date`; backend falls back to `purchase_date`
|
||
|
||
---
|
||
|
||
## 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 ||--o{ grn_attachments : documents
|
||
grn_items ||--o{ assets : auto_created
|
||
|
||
items ||--o{ purchase_order_items : ordered
|
||
items ||--o{ grn_items : received
|
||
|
||
item_categories ||--o{ item_subcategories : has
|
||
item_categories ||--o{ items : classifies
|
||
item_categories ||--o{ assets : classifies
|
||
item_subcategories ||--o{ items : sub_classifies
|
||
item_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 : location
|
||
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) |
|
||
| GRN attachments | `POST /grn/:grnId/attachments`, `GET .../download` |
|
||
| 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)
|