Skip to main content

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 di organization_units
  • location_type_key (string) – Harus ada di master core_location_types
  • name (string) – Nama lengkap lokasi
  • code (string) – Kode lokasi (unik dalam scope organization unit yang sama)
  • category_key (string) – Kategori lokasi
  • is_active (boolean) – Status aktif

Optional Fields

  • op_unit_id (UUID) – Operational unit; harus ada di operational_units jika diberikan
  • parent_location_id (UUID, nullable) – Parent lokasi untuk hierarki; null = root
  • short_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_active harus true

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_key berubah, hierarchy di-revalidasi terhadap parent (baru atau lama).
  • Jika code berubah, uniqueness di-revalidasi dalam scope org_unit_id (create menggunakan org_unit_id dari 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

HTTPReason CodeTriggerSolution / Note
404location.not-foundLokasi tidak ditemukanCek ID valid
404location.parent-not-foundParent tidak ditemukanCek parent_id
400location.parent-inactiveParent tidak aktifAktifkan parent atau pilih lain
404location.type-not-foundlocation_type_key tidak adaCek core_location_types
400location.type-hierarchy-invalidType kind tidak boleh child dari parent type kindCek core_location_type_hierarchy_rule
400location.circular-reference-selfparent_id = id lokasi itu sendiriPilih parent lain
400location.circular-reference-descendantParent baru adalah descendantPilih parent di luar subtree
400location.code-not-uniqueCode duplikat dalam org unitGunakan code unik dalam org_unit_id
400location.has-active-childrenSoft delete / deactivate saat masih ada child aktifHapus/reassign children dulu
400location.not-soft-deletedHard delete tanpa soft deleteLakukan soft delete dulu
400location.has-childrenHard delete saat masih punya childrenHapus children dulu

  • 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