Validation Rules Documentation
Overview
Dokumentasi aturan validasi untuk Location API. Sumber implementasi: validation/location.validator.ts dan controller.
Validation Rules
CREATE Location
Required Fields
org_unit_id(UUID) – Organization unit (branch) yang menjadi pemilik lokasi; harus ada diorganization_unitslocation_type_key(string) – Harus ada di mastercore_location_typesname(string) – Nama lengkap lokasicode(string) – Kode lokasi (unik dalam scope organization unit yang sama)category_key(string) – Kategori lokasiis_active(boolean) – Status aktif
Optional Fields
op_unit_id(UUID) – Operational unit; harus ada dioperational_unitsjika diberikanparent_location_id(UUID, nullable) – Parent lokasi untuk hierarki; null = rootshort_name(string)slug(string)address(string)latitude(number, -90 s.d. 90)longitude(number, -180 s.d. 180)attributes(object)
Business Rules
1. Parent Validation
Rule: Jika parent_location_id diberikan, parent harus ada dan aktif.
Validations:
- Parent UUID harus ada di database
- Parent
is_activeharustrue
Error Responses:
// Parent not found
{
"statusCode": 404,
"message": "Parent location not found",
"reason": "location.parent-not-found"
}
// Parent inactive
{
"statusCode": 400,
"message": "Parent location is inactive",
"reason": "location.parent-inactive"
}
2. Type Validation
Rule: location_type_key harus ada di tabel core_location_types.
Error Response:
{
"statusCode": 404,
"message": "Location type not found",
"reason": "location.type-not-found"
}
3. Hierarchy Validation (Type Kind)
Rule: Relasi parent–child type diatur oleh master core_location_type_hierarchy_rule. Child type kind hanya boleh menjadi child dari parent type kind jika rule mengizinkan (allow = true). Berbeda dengan level-based, locations memakai type kind (typeKindKey).
Valid Examples:
- ✅ warehouse → storage_area (jika rule allow)
- ✅ storage_area → shelf
- ✅ parent_location_id: null → warehouse (root)
Invalid Examples:
- ❌ warehouse → shelf (jika rule tidak mengizinkan)
- ❌ shelf → warehouse
Error Response:
{
"statusCode": 400,
"message": "Location type hierarchy is not allowed. Child type kind cannot be a child of parent type kind.",
"reason": "location.type-hierarchy-invalid",
"details": {
"parentTypeKind": "warehouse",
"childTypeKind": "shelf"
}
}
4. Code Uniqueness
Rule: code harus unik dalam organization unit yang sama (hanya cek lokasi aktif).
Error Response:
{
"statusCode": 400,
"message": "Location code must be unique within the same organization unit",
"reason": "location.code-not-unique"
}
UPDATE Location
Update mendukung partial update; semua field optional, termasuk parent_location_id. Perubahan parent dapat dilakukan lewat Update maupun endpoint Move.
Business Rules
1. Existence Check
Rule: Lokasi harus ada sebelum update.
Error Response:
{
"statusCode": 404,
"message": "Location not found",
"reason": "location.not-found"
}
2. Parent Change – Circular Reference
Jika parent_location_id berubah, validasi circular reference diterapkan.
Self: Tidak boleh set parent = id lokasi itu sendiri.
{
"statusCode": 400,
"message": "Location cannot be its own parent",
"reason": "location.circular-reference-self"
}
Descendant: Parent baru tidak boleh lokasi yang merupakan descendant (anak/cucu) dari lokasi saat ini. Pengecekan memakai pathLtree: jika path parent baru diawali path lokasi saat ini = circular.
{
"statusCode": 400,
"message": "Cannot set parent to a descendant location",
"reason": "location.circular-reference-descendant"
}
3. Allow Parent = null
Rule: parent_location_id boleh di-set ke null untuk menjadikan lokasi sebagai root.
4. Hierarchy & Code Re-validation
- Jika
location_type_keyberubah, hierarchy di-revalidasi terhadap parent (baru atau lama). - Jika
codeberubah, uniqueness di-revalidasi dalam scopeorg_unit_id(create menggunakanorg_unit_iddari DTO atau current location).
MOVE Location
Endpoint: POST /locations/:id/move?new_parent_id=<uuid>
Memindahkan lokasi ke parent baru. Validasi sama dengan perubahan parent di Update:
- New parent harus ada dan aktif →
location.parent-not-found,location.parent-inactive - Tidak boleh self →
location.circular-reference-self - Tidak boleh descendant →
location.circular-reference-descendant - Type hierarchy harus valid →
location.type-hierarchy-invalid
TOGGLE STATUS (Activate/Deactivate)
Rule: Tidak boleh deactivate jika lokasi masih memiliki active children.
Error Response:
{
"statusCode": 400,
"message": "Cannot deactivate location with active children",
"reason": "location.has-active-children"
}
What to do: Deactivate atau reassign children terlebih dahulu.
DELETE Location (Soft Delete)
Business Rules
1. Has Active Children
Rule: Lokasi yang masih punya child aktif tidak boleh di-soft-delete.
Error Response:
{
"statusCode": 400,
"message": "Cannot delete location with active children",
"reason": "location.has-active-children"
}
2. Has Inventory (Planned)
Rule: (TODO) Tidak boleh delete jika lokasi masih memiliki inventory items. Belum diimplementasi; akan menggunakan location.has-inventory bila tersedia.
3. Soft Delete Behavior
- Set
is_active = false(atau mekanisme soft delete yang dipakai) - Data tetap tersimpan; hard delete hanya setelah soft delete dan memenuhi syarat
HARD DELETE Location
Business Rules
1. Must Be Soft-Deleted First
Rule: Lokasi harus sudah di-soft-delete (deletedAt terisi) sebelum hard delete.
Error Response:
{
"statusCode": 400,
"message": "Location must be soft deleted first",
"reason": "location.not-soft-deleted"
}
2. No Children
Rule: Lokasi tidak boleh masih punya children (aktif maupun tidak) saat hard delete.
Error Response:
{
"statusCode": 400,
"message": "Cannot hard delete location with children",
"reason": "location.has-children"
}
3. Not Found
Error Response:
{
"statusCode": 404,
"message": "Location not found",
"reason": "location.not-found"
}
Validation Flow Diagrams
CREATE Flow
┌─────────────────┐
│ Validate DTO │ (class-validator + UuidExists)
└────────┬────────┘
↓
┌─────────────────┐
│ validateCreate()│
└────────┬────────┘
↓
┌────────────────────┐
│ Has parent_location_id? │
└────┬───────────┬───┘
│ Yes │ No
↓ │
┌───────────────┐│
│validateParent ││
│- Exists? ││
│- Active? ││
└────┬──────────┘│
↓ ↓
┌────────────────────┐
│ validateType │
│ - type exists? │
└────────┬───────────┘
↓
┌────────────────────┐
│ Has parent & type? │
└────┬───────────┬────┘
│ Yes │ No
↓ ↓
┌────────────────────┐
│validateHierarchy │
│(core_location_type_│
│ hierarchy_rule) │
└────┬──────────────┘
↓
┌────────────────────┐
│ Code uniqueness │
│ within org_unit? │
└────┬───────────────┘
↓
┌────────────────────┐
│ CREATE LOCATION │
└────────────────────┘
UPDATE Flow
┌─────────────────┐
│ Validate DTO │ (partial, all optional)
└────────┬────────┘
↓
┌─────────────────┐
│validateUpdate() │
└────────┬────────┘
↓
┌─────────────────┐
│validateExistence│
└────────┬────────┘
↓
┌─────────────────────────────┐
│ parent_location_id changed? │
└────┬───────────────────┬────┘
│ Yes (non-null) │ No
↓ ↓
┌──────────────────────┐ ┌────────────────┐
│ validateParent │ │ Use current │
│ validateCircularRef │ │ parent for │
│ - Not self? │ │ hierarchy check│
│ - Not descendant? │ └────────────────┘
└────┬─────────────────┘
↓
┌─────────────────────┐
│ validateType & │
│ validateHierarchy │
└────────┬────────────┘
↓
┌─────────────────┐
│ Code changed? │ → validate uniqueness
└────────┬────────┘
↓
┌────────────────────┐
│ UPDATE LOCATION │
└────────────────────┘
SOFT DELETE Flow
┌─────────────────┐
│ validateDelete()│
└────────┬────────┘
↓
┌─────────────────┐
│ hasChildren(id) │
│ (active only) │
└────────┬────────┘
↓
┌────────────────┐
│ Active children?│
└────┬───────┬────┘
│ Yes │ No
↓ ↓
┌──────┐ ┌─────────────┐
│ 400 │ │ SOFT DELETE │
│has- │ │ is_active │
│active-│ │ = false │
│children│ └─────────────┘
└──────┘
HARD DELETE Flow
┌──────────────────┐
│validateHardDelete│
└────────┬─────────┘
↓
┌─────────────────┐
│ Location exists?│
└────┬────────┬───┘
│ No │ Yes
↓ ↓
┌──────┐ ┌─────────────────┐
│ 404 │ │ deletedAt 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 | location.not-found | Lokasi tidak ditemukan | Cek ID valid |
| 404 | location.parent-not-found | Parent tidak ditemukan | Cek parent_id |
| 400 | location.parent-inactive | Parent tidak aktif | Aktifkan parent atau pilih lain |
| 404 | location.type-not-found | location_type_key tidak ada | Cek core_location_types |
| 400 | location.type-hierarchy-invalid | Type kind tidak boleh child dari parent type kind | Cek core_location_type_hierarchy_rule |
| 400 | location.circular-reference-self | parent_id = id lokasi itu sendiri | Pilih parent lain |
| 400 | location.circular-reference-descendant | Parent baru adalah descendant | Pilih parent di luar subtree |
| 400 | location.code-not-unique | Code duplikat dalam org unit | Gunakan code unik dalam org_unit_id |
| 400 | location.has-active-children | Soft delete / deactivate saat masih ada child aktif | Hapus/reassign children dulu |
| 400 | location.not-soft-deleted | Hard delete tanpa soft delete | Lakukan soft delete dulu |
| 400 | location.has-children | Hard delete saat masih punya children | Hapus children dulu |
Related Files
- Validator:
src/locations/validation/location.validator.ts - Controller:
src/locations/locations.controller.ts - DTOs:
dto/create-location.dto.ts,dto/update-location.dto.ts - PRD:
docs/locations.md - Technical:
docs/TEKNIS.md,docs/README.md