Skip to main content

Validation Rules Documentation

Overview

Dokumentasi aturan validasi untuk Operational Unit API. Sumber implementasi: validation/operational-unit.validator.ts dan controller.


Validation Rules

CREATE Operational Unit

Required Fields

  • name (string, max 100) – Nama unit operasional
  • code (string, max 50) – Kode unit (unik)
  • type_key (string) – Harus ada di master core_operational_unit_types
  • is_active (boolean) – Status aktif

Optional Fields

  • parent_id (UUID, nullable) – Parent unit; jika tidak ada, unit dibuat sebagai root

Business Rules

1. Parent Validation

Rule: Jika parent_id diberikan, parent harus ada, tidak terhapus, dan aktif.

Validations:

  • Parent UUID harus ada di database
  • Parent deletedAt harus null
  • Parent is_active harus true

Error Responses:

// Parent not found
{
"statusCode": 404,
"message": "Parent operational unit not found",
"reason": "operational-unit.parent-not-found"
}

// Parent deleted
{
"statusCode": 404,
"message": "Parent operational unit is deleted",
"reason": "operational-unit.parent-deleted"
}

// Parent inactive
{
"statusCode": 400,
"message": "Parent operational unit is inactive",
"reason": "operational-unit.parent-inactive"
}
2. Type Validation

Rule: type_key harus ada di tabel core_operational_unit_types.

Error Response:

{
"statusCode": 404,
"message": "Operational unit type not found",
"reason": "operational-unit.type-not-found"
}
3. Hierarchy Validation

Rule: Level tipe unit (child) harus lebih tinggi dari level tipe parent (type.levelOrder child > parent).

Example hierarchy (entity → region → zone → area → site):

Entity (level 1)
└─ Region (level 2)
└─ Zone (level 3)
└─ Area (level 4)
└─ Site (level 5)

Valid:

  • ✅ Parent: Entity (level 1) → Child: Region (level 2)
  • ✅ Parent: Region (level 2) → Child: Zone (level 3)
  • ✅ parent_id: null → Child: Entity (level 1) — root

Invalid:

  • ❌ Parent: Region (level 2) → Child: Entity (level 1)
  • ❌ Parent: Area (level 4) → Child: Area (level 4) — same level

Error Response:

{
"statusCode": 400,
"message": "Operational unit type level must be higher than parent type level",
"reason": "operational-unit.type-hierarchy-invalid",
"details": {
"parentTypeLevel": 2,
"currentTypeLevel": 1
}
}

UPDATE Operational Unit

Perubahan parent tidak dilakukan lewat update; gunakan endpoint Move untuk mengubah parent.

All Fields Optional

Hanya field yang dikirim yang di-update (partial update): name, code, type_key, is_active.

Business Rules

1. Existence Check

Rule: Unit harus ada sebelum update.

Error Response:

{
"statusCode": 404,
"message": "Operational unit not found",
"reason": "operational-unit.not-found"
}
2. Type & Hierarchy Re-validation

Rule: Jika type_key diubah, hierarki di-revalidasi terhadap parent saat ini (child type level harus > parent type level).


MOVE Operational Unit (Change Parent)

Rule: Parent baru tidak boleh diri sendiri dan tidak boleh descendant dari unit yang dipindah.

1. Circular Reference – Self

Rule: Unit tidak boleh menjadi parent dirinya sendiri.

Error Response:

{
"statusCode": 400,
"message": "Operational unit cannot be its own parent",
"reason": "operational-unit.circular-reference-self"
}
2. Circular Reference – Descendant

Rule: Parent baru tidak boleh unit yang merupakan descendant (anak/cucu) dari unit saat ini. Pengecekan memakai kolom path (pathLtree): jika path parent baru diawali path unit saat ini = circular.

Invalid Example:

Current: Entity A → Region B → Zone C
Move Zone C → parent = Entity A ❌ (Entity A adalah ancestor dari C)

Valid Example:

Current: Entity A → Region B → Zone C
Move Zone C → parent = Region D ✅ (D bukan descendant C)

Error Response:

{
"statusCode": 400,
"message": "Cannot set parent to a descendant operational unit",
"reason": "operational-unit.circular-reference-descendant"
}

Controller dapat mengembalikan reason generik operational-unit.circular-reference untuk kasus circular; detail self vs descendant lihat pesan message.

3. Parent & Type Compatibility

Move juga memvalidasi: parent baru ada, aktif, dan tipe unit kompatibel dengan tipe parent baru. Error yang mungkin: operational-unit.parent-not-found, operational-unit.type-not-found, operational-unit.type-incompatible.


DELETE Operational Unit (Soft Delete)

Business Rules

1. Has Active Children

Rule: Unit yang masih punya child aktif tidak boleh di-soft-delete.

What to do:

  1. Soft-delete atau deactivate semua child dulu, atau
  2. Pindahkan child ke parent lain (Move), lalu delete

Error Response:

{
"statusCode": 400,
"message": "Cannot delete operational unit with active children",
"reason": "operational-unit.has-active-children"
}
2. Soft Delete Behavior
  • Unit ditandai deleted (mis. deletedAt, deletedBy di-set)
  • Data tetap ada; tidak hard delete
  • Hard delete hanya boleh setelah soft delete dan memenuhi syarat (lihat Hard Delete)

Catatan: Beberapa flow (mis. toggle active / deactivate) mungkin memerlukan unit tidak punya active owners/positions; error operational-unit.has-active-owners, operational-unit.has-active-positions dapat dikembalikan controller saat operasi yang melibatkan status/dependency.


HARD DELETE Operational Unit

Business Rules

1. Must Be Soft-Deleted First

Rule: Unit harus sudah di-soft-delete (deletedAt dan deletedBy terisi) sebelum hard delete.

Error Response:

{
"statusCode": 400,
"message": "Operational unit must be soft deleted first",
"reason": "operational-unit.not-soft-deleted"
}
2. No Children

Rule: Unit tidak boleh masih punya child (aktif maupun sudah soft-deleted) saat hard delete.

Error Response:

{
"statusCode": 400,
"message": "Cannot hard delete operational unit with children",
"reason": "operational-unit.has-children"
}
3. Not Found

Error Response:

{
"statusCode": 404,
"message": "Operational unit not found",
"reason": "operational-unit.not-found"
}

Jika masih ada referensi dari modul lain yang menghalangi penghapusan, controller dapat mengembalikan:

{
"reason": "operational-unit.related-record-exists"
}

Assign / Remove Owner

  • Assign owner: Unit harus ada; employee_id harus valid. Jika owner sudah terdaftar: operational-unit.owner-already-exists. Jika employee tidak ditemukan: employee.not-found.
  • Remove owner: Unit dan owner harus ada; jika owner tidak terdaftar di unit: operational-unit.owner-not-found.
  • Unit not found: operational-unit.not-found.

Validation Flow Diagrams

CREATE Flow

┌─────────────────┐
│ Validate DTO │ (class-validator: name, code, type_key, is_active, parent_id?)
└────────┬────────┘

┌─────────────────┐
│ validateCreate()│
└────────┬────────┘

┌────────────────────┐
│ Has parent_id? │
└────┬───────────┬───┘
│ Yes │ No
↓ │
┌───────────────┐│
│validateParent ││
│- Exists? ││
│- Not deleted? ││
│- Active? ││
└────┬──────────┘│
↓ ↓
┌────────────────────┐
│ validateType │
│ - type_key exists│
└────────┬───────────┘

┌────────────────────┐
│ Has parent & type? │
└────┬───────────┬────┘
│ Yes │ No
↓ ↓
┌───────────────────┐
│validateHierarchy │
│type.levelOrder > │
│parent.type.levelOrder?
└────┬──────────────┘

┌────────────────────┐
│ CREATE UNIT │
└────────────────────┘

UPDATE Flow

┌─────────────────┐
│ Validate DTO │ (optional: name, code, type_key, is_active)
└────────┬────────┘

┌─────────────────┐
│validateUpdate() │
└────────┬────────┘

┌─────────────────┐
│validateExistence│
│- Unit exists? │
└────────┬────────┘

┌─────────────────┐
│ Get current │
│ parent & type │
└────────┬────────┘

┌─────────────────┐
│ type_key changed?│
└────┬────────┬────┘
│ Yes │ No
↓ ↓
┌────────────┐ │
│validateType│ │
│validateHierarchy (current parent + new type)
└────┬───────┘ │
↓ ↓
┌────────────────────┐
│ UPDATE UNIT │
└────────────────────┘

MOVE Flow

┌─────────────────┐
│ new_parent_id │
└────────┬────────┘

┌─────────────────────────────┐
│ validateCircularReference │
│ - currentUnitId !== newParentId?
│ - newParent path NOT under │
│ current unit path? │
└────────┬────────────────────┘

┌─────────────────┐
│ validateParent │
│ validateType │
│ (compatibility) │
└────────┬────────┘

┌────────────────────┐
│ MOVE (recalc path)│
└────────────────────┘

SOFT DELETE Flow

┌─────────────────┐
│ validateDelete()│
└────────┬────────┘

┌─────────────────┐
│ hasChildren(id) │
│ (active only) │
└────────┬────────┘

┌────────────────┐
│ Active children?│
└────┬───────┬────┘
│ Yes │ No
↓ ↓
┌──────┐ ┌─────────────┐
│ 400 │ │ SOFT DELETE │
│has- │ │ set deletedAt│
│active-│ │ deletedBy │
│children│ └─────────────┘
└──────┘

HARD DELETE Flow

┌──────────────────┐
│validateHardDelete│
└────────┬─────────┘

┌─────────────────┐
│ Unit exists? │
└────┬────────┬───┘
│ No │ Yes
↓ ↓
┌──────┐ ┌─────────────────┐
│ 404 │ │ deletedAt set? │
└──────┘ │ deletedBy set? │
└────┬────────┬───┘
│ No │ Yes
↓ ↓
┌──────┐ ┌─────────────┐
│ 400 │ │ hasChildren?│
│not- │ └────┬────┬───┘
│soft- │ │Yes │No
│deleted ↓ ↓
└──────┘ ┌──────┐ ┌─────┐
│ 400 │ │HARD │
│has- │ │DELETE
│children│ └─────┘
└──────┘

Error Code Reference

HTTPReason CodeTriggerSolution / Note
404operational-unit.not-foundUnit ID tidak ditemukanPastikan ID valid
404operational-unit.parent-not-foundParent ID tidak ditemukanPastikan parent ada
404operational-unit.parent-deletedParent sudah di-soft-deletePilih parent lain
400operational-unit.parent-inactiveParent tidak aktifAktifkan parent atau pilih lain
404operational-unit.type-not-foundtype_key tidak ada di masterGunakan key dari core_operational_unit_types
400operational-unit.type-hierarchy-invalidLevel tipe ≤ level parentGunakan tipe dengan level lebih tinggi
400operational-unit.type-incompatibleTipe tidak cocok (e.g. saat move)Pilih parent dengan tipe yang kompatibel
400operational-unit.circular-reference-selfparent_id = id unit itu sendiriPilih parent lain
400operational-unit.circular-reference-descendantParent baru adalah descendantPilih parent di luar subtree
400operational-unit.circular-referenceCircular (generic)Umumnya self atau descendant
409operational-unit.unit-inactiveUnit tidak aktifKonteks operasi yang butuh unit aktif
400operational-unit.has-active-childrenSoft delete saat masih ada child aktifHapus/pindahkan child dulu
409operational-unit.has-active-ownersMasih ada owner aktifLepas owner dulu (konteks deactivate/delete)
409operational-unit.has-active-positionsMasih ada position aktifLepas position dulu
400operational-unit.not-soft-deletedHard delete tanpa soft deleteLakukan soft delete dulu
400operational-unit.has-childrenHard delete saat masih punya childHapus child dulu (soft/hard)
409operational-unit.soft-delete-requiredHarus soft delete duluSama dengan not-soft-deleted
409operational-unit.related-record-existsAda referensi dari modul lainHapus atau putus referensi dulu
409operational-unit.owner-already-existsOwner sudah di-assignJangan assign duplikat
404operational-unit.owner-not-foundOwner tidak terdaftar di unitCek employee_id / assignment
404employee.not-foundemployee_id tidak ditemukanCek master employee

  • Validator: src/operational-units/validation/operational-unit.validator.ts
  • Controller: src/operational-units/operational-units.controller.ts
  • DTOs: dto/create-operational-unit.dto.ts, dto/update-operational-unit.dto.ts
  • PRD & technical: docs/operation_units.md, docs/for_developer.md (di lania-documentation atau repo terkait)