Validation Rules
π Overviewβ
Dokumentasi lengkap untuk validation rules yang diterapkan pada Organization Unit API endpoints.
π Validation Rulesβ
CREATE Organization Unitβ
Required Fieldsβ
name(string) - Organization unit nametype_key(string) - Must exist incore_organization_unit_typestableis_active(boolean) - Active status
Optional Fieldsβ
parent_id(UUID) - Parent organization unitshort_name(string) - Short name or abbreviationslug(string) - URL-friendly slug (auto-generated if not provided)code(string) - Organization unit codeattributes(object) - Custom JSON attributesinformation(object) - Contact informationmodules(array) - Module availabilityowners(array) - Employee ownerstag_ids(array) - Tag UUIDsdocuments(array) - Document attachmentsbank_ids(array) - Company bank UUIDs
Business Rulesβ
1. Parent Validationβ
Rule: If parent_id is provided, the parent must exist and be active.
Validations:
- Parent UUID must exist in database
- Parent
is_activemust betrue
Error Codes:
// Parent not found
{
"statusCode": 404,
"message": "Parent organization unit not found",
"reason": "organization-unit.parent-not-found"
}
// Parent inactive
{
"statusCode": 400,
"message": "Parent organization unit is inactive",
"reason": "organization-unit.parent-inactive"
}
2. Type Validationβ
Rule: type_key must exist in core_organization_unit_types table.
Error Code:
{
"statusCode": 404,
"message": "Organization unit type not found",
"reason": "organization-unit.type-not-found"
}
3. Hierarchy Validationβ
Rule: Child type level must be higher than parent type level.
Example Hierarchy:
Company (level 1)
ββ Branch (level 2)
ββ Department (level 3)
ββ Team (level 4)
Valid Examples:
- β Parent: Company (level 1) β Child: Branch (level 2)
- β Parent: Branch (level 2) β Child: Department (level 3)
- β Parent: null β Child: Company (level 1) - root level
Invalid Examples:
- β Parent: Department (level 3) β Child: Branch (level 2)
- β Parent: Branch (level 2) β Child: Branch (level 2) - same level
Error Code:
{
"statusCode": 400,
"message": "Organization unit type level must be higher than parent type level",
"reason": "organization-unit.type-hierarchy-invalid",
"details": {
"parentTypeLevel": 2,
"currentTypeLevel": 1
}
}
UPDATE Organization Unitβ
All Fields Optionalβ
When updating, all fields are optional (partial update). Only provided fields will be updated.
Additional Validation Rulesβ
1. Existence Checkβ
Rule: Organization unit must exist before update.
Error Code:
{
"statusCode": 404,
"message": "Organization unit not found",
"reason": "organization-unit.not-found"
}
2. Circular Reference - Selfβ
Rule: Cannot set unit as its own parent.
Invalid Example:
{
"parent_id": "same-as-current-unit-id"
}
Error Code:
{
"statusCode": 400,
"message": "Organization unit cannot be its own parent",
"reason": "organization-unit.circular-reference-self"
}
3. Circular Reference - Descendantβ
Rule: Cannot set parent to a descendant unit (prevents circular hierarchy).
How it works: Uses PostgreSQL ltree path to check if new parent is a descendant.
Invalid Example:
Current hierarchy: A β B β C
Trying to update: C.parent_id = A β (A is ancestor of C)
Valid Example:
Current hierarchy: A β B β C
Trying to update: C.parent_id = D β
(D is not related)
Error Code:
{
"statusCode": 400,
"message": "Cannot set parent to a descendant organization unit",
"reason": "organization-unit.circular-reference-descendant"
}
4. Hierarchy Re-validationβ
Rule: When changing parent_id or type_key, hierarchy rules are re-validated.
- If only
parent_idchanges: Use new parent + existing type - If only
type_keychanges: Use existing parent + new type - If both change: Use new parent + new type
- Same hierarchy validation as CREATE applies
DELETE Organization Unit (Soft Delete)β
Validation Rulesβ
1. Has Active Childrenβ
Rule: Cannot delete a unit that still has active children.
What to do:
- Delete/deactivate all children first, OR
- Reassign children to different parent
Error Code:
{
"statusCode": 400,
"message": "Cannot delete organization unit with active children",
"reason": "organization-unit.has-active-children"
}
2. Soft Delete Behaviorβ
- Sets
is_active = false - Data is preserved (not hard deleted)
- Can be reactivated by setting
is_active = truevia update
π― Validation Flow Diagramsβ
CREATE Flowβ
βββββββββββββββββββ
β Validate DTO β (class-validator)
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ
β validateCreate()β
ββββββββββ¬βββββββββ
β
ββββββββββββββββββββββ
β Has parent_id? β
ββββββ¬ββββββββββββ¬ββββ
β Yes β No
β β
βββββββββββββ β
βvalidateParentβ β
β- Exists? β β
β- Active? β β
ββββββ¬βββββββββ β
β β
ββββββββββββββββββββββ
β validateType β
β - Exists? β
ββββββββββ¬ββββββββββββ
β
ββββββββββββββββββββββ
βHas parent & type? β
ββββββ¬ββββββββββββ¬ββββ
β Yes β No
β β
ββββββββββββββββββ
βvalidateHierarchyββ
βType level > ββ
βParent level? ββ
ββββββ¬ββββββββββββ
β β
ββββββββββββββββββββββ
β CREATE UNIT β
ββββββββββββββββββββββ
UPDATE Flowβ
βββββββββββββββββββ
β Validate DTO β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ
βvalidateUpdate() β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ
βvalidateExistenceβ
β- Unit exists? β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββββββββ
βparent_id changed? β
ββββββ¬βββββββββββββββ¬ββββ
β Yes β No
β β
ββββββββββββββββ β
βvalidateParentβ β
βvalidateCircularβ β
β- Not self? β β
β- Not descendant?β β
ββββββ¬βββββββββββ β
β β
βββββββββββββββββββββββββ
β Validate Hierarchy β
β (with new/old data) β
ββββββββββ¬βββββββββββββββ
β
ββββββββββββββββββββββ
β UPDATE UNIT β
ββββββββββββββββββββββ
DELETE Flowβ
βββββββββββββββββββ
βvalidateDelete() β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ
β hasChildren() β
ββββββββββ¬βββββββββ
β
ββββββββββββββββββ
βHas active kids?β
ββββββ¬ββββββββ¬ββββ
β Yes β No
β β
ββββββββ βββββββββββ
βREJECTβ βSOFT DEL β
β 400 β βis_activeβ
ββββββββ β= false β
βββββββββββ
π Error Code Referenceβ
Complete Error Codes Listβ
| HTTP | Reason Code | Trigger | Solution |
|---|---|---|---|
| 404 | organization-unit.not-found | Unit ID not found | Check unit ID exists |
| 404 | organization-unit.parent-not-found | Parent ID not found | Check parent ID exists |
| 400 | organization-unit.parent-inactive | Parent is inactive | Use active parent or set parent to null |
| 404 | organization-unit.type-not-found | Type key not found | Check type exists in core_organization_unit_types |
| 400 | organization-unit.type-hierarchy-invalid | Type level β€ parent level | Use higher type level than parent |
| 400 | organization-unit.circular-reference-self | parent_id = current unit ID | Choose different parent |
| 400 | organization-unit.circular-reference-descendant | parent is descendant | Choose parent that is not in subtree |
| 400 | organization-unit.has-active-children | Deleting unit with children | Delete/reassign children first |
π§ͺ Testing Scenariosβ
Test Case: CREATE Successβ
POST /organization-units
{
"name": "Jakarta Branch",
"type_key": "branch",
"parent_id": "company-uuid",
"is_active": true
}
// Assuming:
// - company-uuid exists and is active
// - company type level = 1
// - branch type level = 2
// Result: β
Success (2 > 1)
Test Case: CREATE Hierarchy Errorβ
POST /organization-units
{
"name": "Head Office",
"type_key": "company",
"parent_id": "branch-uuid",
"is_active": true
}
// Assuming:
// - branch type level = 2
// - company type level = 1
// Result: β 400 type-hierarchy-invalid (1 β€ 2)
Test Case: UPDATE Circular Referenceβ
// Current: Company A β Branch B β Dept C
PATCH /organization-units/company-a-uuid
{
"parent_id": "dept-c-uuid"
}
// Result: β 400 circular-reference-descendant
// (Dept C is descendant of Company A)
Test Case: DELETE with Childrenβ
// Current: Branch A has 3 active departments
DELETE /organization-units/branch-a-uuid
// Result: β 400 has-active-children
π‘ Best Practicesβ
For API Consumersβ
-
Always validate hierarchy before submit
- Check parent type level < child type level
- Use GET
/core/organization-unit-typesto get type levels
-
Handle error codes properly
- Use
reasonfield for i18n/translation - Show user-friendly messages based on reason code
- Use
-
Pre-check before update parent
- Verify new parent is not self
- Verify new parent is not in unit's subtree
-
Bulk operations
- Create from top-down (parent first, then children)
- Delete from bottom-up (children first, then parent)
For Frontend Developersβ
// Example: Pre-validate before submit
function canSetParent(unitId: string, newParentId: string, units: Unit[]) {
// Check 1: Not self
if (unitId === newParentId) return false;
// Check 2: Not descendant (check if newParent is in unit's subtree)
const unit = units.find((u) => u.id === unitId);
const newParent = units.find((u) => u.id === newParentId);
if (newParent.path_ltree.startsWith(unit.path_ltree)) {
return false; // newParent is descendant
}
return true;
}
// Example: Handle validation errors
const handleError = (error) => {
const { reason, message, details } = error.response.data;
switch (reason) {
case 'organization-unit.type-hierarchy-invalid':
showError(
`Type level must be higher than parent. ` +
`Parent level: ${details.parentTypeLevel}, ` +
`Current level: ${details.currentTypeLevel}`,
);
break;
case 'organization-unit.circular-reference-descendant':
showError('Cannot set parent to a child unit');
break;
default:
showError(message);
}
};
π Related Documentationβ
- ERROR-CODES.md - Complete error codes reference
- validation/README.md - Validator implementation details
- Swagger UI - Interactive API documentation