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 operasionalcode(string, max 50) – Kode unit (unik)type_key(string) – Harus ada di mastercore_operational_unit_typesis_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
deletedAtharusnull - Parent
is_activeharustrue
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:
- Soft-delete atau deactivate semua child dulu, atau
- 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,deletedBydi-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"
}
4. Related Records (Controller)
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_idharus 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
| HTTP | Reason Code | Trigger | Solution / Note |
|---|---|---|---|
| 404 | operational-unit.not-found | Unit ID tidak ditemukan | Pastikan ID valid |
| 404 | operational-unit.parent-not-found | Parent ID tidak ditemukan | Pastikan parent ada |
| 404 | operational-unit.parent-deleted | Parent sudah di-soft-delete | Pilih parent lain |
| 400 | operational-unit.parent-inactive | Parent tidak aktif | Aktifkan parent atau pilih lain |
| 404 | operational-unit.type-not-found | type_key tidak ada di master | Gunakan key dari core_operational_unit_types |
| 400 | operational-unit.type-hierarchy-invalid | Level tipe ≤ level parent | Gunakan tipe dengan level lebih tinggi |
| 400 | operational-unit.type-incompatible | Tipe tidak cocok (e.g. saat move) | Pilih parent dengan tipe yang kompatibel |
| 400 | operational-unit.circular-reference-self | parent_id = id unit itu sendiri | Pilih parent lain |
| 400 | operational-unit.circular-reference-descendant | Parent baru adalah descendant | Pilih parent di luar subtree |
| 400 | operational-unit.circular-reference | Circular (generic) | Umumnya self atau descendant |
| 409 | operational-unit.unit-inactive | Unit tidak aktif | Konteks operasi yang butuh unit aktif |
| 400 | operational-unit.has-active-children | Soft delete saat masih ada child aktif | Hapus/pindahkan child dulu |
| 409 | operational-unit.has-active-owners | Masih ada owner aktif | Lepas owner dulu (konteks deactivate/delete) |
| 409 | operational-unit.has-active-positions | Masih ada position aktif | Lepas position dulu |
| 400 | operational-unit.not-soft-deleted | Hard delete tanpa soft delete | Lakukan soft delete dulu |
| 400 | operational-unit.has-children | Hard delete saat masih punya child | Hapus child dulu (soft/hard) |
| 409 | operational-unit.soft-delete-required | Harus soft delete dulu | Sama dengan not-soft-deleted |
| 409 | operational-unit.related-record-exists | Ada referensi dari modul lain | Hapus atau putus referensi dulu |
| 409 | operational-unit.owner-already-exists | Owner sudah di-assign | Jangan assign duplikat |
| 404 | operational-unit.owner-not-found | Owner tidak terdaftar di unit | Cek employee_id / assignment |
| 404 | employee.not-found | employee_id tidak ditemukan | Cek master employee |
Related Files
- 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)