diff --git a/docs/MODULE_DATA_FLOW.html b/docs/MODULE_DATA_FLOW.html new file mode 100644 index 0000000..2dbff02 --- /dev/null +++ b/docs/MODULE_DATA_FLOW.html @@ -0,0 +1,1307 @@ + + + + + + ERP Backend — Module & Data Flow Diagram + + + + +
+ + +
+

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/* (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
+        
+
+ + + + + + + +
LayerStorageReset
Global API rate limitIn-memory per IPRestart server or wait 15 min
Login rate limitIn-memory per IPRestart or wait 15 min
Account lockoutusers.locked_untilWait 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 AssetSource
asset_codedocument_series via ASSET_{category.code}
asset_nameItem name (+ #N if qty > 1)
asset_category_id / asset_subcategory_idFrom GRN line payload
plant_idFrom PO
warehouse_id, vendor_id, po_id, grn_id, grn_item_idFrom GRN
purchase_dategrn.grn_date
purchase_costgrn_items.rate
useful_life_years, depreciation_methodFrom asset_categories defaults
conditionNEW
statusIN_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

+ + + + + + +
FieldValues
statusIN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED
conditionNEW, 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 (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

+ + +

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 +
+
+ + + + + + + +
MethodRate auto-calc?Formula concept
SLMYes (if rate omitted)Equal yearly depreciation on original cost
WDVYes (if rate omitted)Depreciation on reducing book value year-by-year
OTHERNo — user must send depreciation_rateCustom % on cost
+

FE usage:

+ + +

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

+ + + + + + + + + + + +
AreaEndpointsNotes
CoreGET/POST /assets, GET/PUT/DELETE /assets/:idIncludes computed depreciation on read
TransferPOST /assets/:id/transfer, GET .../transfer-historyBlocked if DISPOSED/SCRAPPED
AMCGET/POST /assets/:id/amc, GET/PUT .../:contractId, PATCH .../renewOne active contract
ServiceGET/POST /assets/:id/service-visits, PUT, PATCH .../statusLinks optional AMC
InsuranceGET/POST /assets/:id/insurance, GET/PUT, PATCH .../renewOne active policy
DepreciationGET /depreciation-methods, POST /depreciation/calculatePreview only
AlertsGET /alerts/expiry, GET /alerts/serviceSQL views
+ +

19. Typical FE screen → API mapping

+ + + + + + + + + + + + + + + + + +
ScreenAPIs used
LoginPOST /auth/login, GET /auth/me
Master setup/masters/* CRUD
Vendor management/vendors/*
Create POMasters dropdowns + POST /purchase-orders
Approve POPOST /purchase-orders/:id/approve
GRN receiptPOST /grn (auto assets for asset items)
Asset list/detailGET /assets, GET /assets/:id
Asset create/edit formMasters + 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 assetPOST /assets/:id/transfer
+ +

20. Renewal concept summary (for FE)

+ + + + + + + + + + + + + + + + +
EntityWhat "Renew" doesOld recordNew record
AMCPATCH /assets/:id/amc/:contractId/renewis_active = falseNew row, is_active = true, new dates
InsurancePATCH /assets/:id/insurance/:policyId/renewis_active = falseNew row, is_active = true, new policy period
+

Both preserve full history — never overwrite the old contract/policy row.

+ +
+

Generated from MODULE_DATA_FLOW.md · Open in any browser · Mermaid diagrams require network for CDN

+
+
+ + + + + + diff --git a/docs/MODULE_DATA_FLOW.md b/docs/MODULE_DATA_FLOW.md new file mode 100644 index 0000000..67699bc --- /dev/null +++ b/docs/MODULE_DATA_FLOW.md @@ -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
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 + +```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
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,
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,
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 + +```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
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_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
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.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
?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 + +```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) diff --git a/src/modules/masters/asset-subcategories/asset-subcategories.service.js b/src/modules/masters/asset-subcategories/asset-subcategories.service.js index d02d9c8..cf15f93 100644 --- a/src/modules/masters/asset-subcategories/asset-subcategories.service.js +++ b/src/modules/masters/asset-subcategories/asset-subcategories.service.js @@ -1,5 +1,6 @@ const prisma = require('../../../config/prisma'); const ApiError = require('../../../utils/ApiError'); +const { getPagination } = require('../../../utils/pagination'); const { buildMasterService } = require('../_shared/master.factory'); const config = { @@ -51,6 +52,21 @@ const 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 category = await prisma.asset_categories.findFirst({ where: { id: BigInt(assetCategoryId), deleted_at: null }, @@ -60,22 +76,83 @@ const assertAssetCategory = async (assetCategoryId) => { 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) => { 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) => { if (payload.asset_category_id !== undefined) { 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 = { createAssetSubcategories, - listAssetSubcategories: service.list, - getAssetSubcategoriesById: service.getOne, + listAssetSubcategories, + getAssetSubcategoriesById, updateAssetSubcategories, deleteAssetSubcategories: service.removeOne, };