gwm : sub categort joining

This commit is contained in:
Gowtham M 2026-07-08 15:02:41 +05:30
parent 22d25bf2f5
commit 6e06006687
3 changed files with 2036 additions and 4 deletions

1307
docs/MODULE_DATA_FLOW.html Normal file

File diff suppressed because it is too large Load Diff

648
docs/MODULE_DATA_FLOW.md Normal file
View File

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

View File

@ -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,
};