erp_be/docs/MODULE_DATA_FLOW.md

26 KiB
Raw Blame History

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_SETUP.md


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/* (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)

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

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

    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)

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

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.

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

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

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

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

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

  • 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

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 (0100) 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 = ((10000010000)/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 previewPOST /assets/depreciation/calculate (no save)
  • Saved asset viewGET /assets/:iddata.depreciation object
  • Method dropdownGET /assets/depreciation-methods
  • Prefer sending commencement_date; backend falls back to purchase_date

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<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

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 ||--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