Dokumentasi Teknis
Overview
Dokumentasi teknis untuk developer yang akan maintain atau develop lebih lanjut pada Locations API module.
Arsitektur
Locations module menggunakan CQRS pattern dengan struktur sebagai berikut:
locations/
├── commands/ # Command handlers untuk write operations
├── queries/ # Query handlers untuk read operations
├── dto/ # Data Transfer Objects
├── mappers/ # Data mappers untuk transformasi data
├── validation/ # Custom validators
├── locations.controller.ts
├── locations.service.ts
├── locations.module.ts
└── docs/ # Dokumentasi
Tech Stack
- Framework: NestJS
- Database: PostgreSQL dengan Prisma ORM
- Hierarchy: PostgreSQL LTree extension untuk path management
- Validation: class-validator, class-transformer
- API Documentation: Swagger/OpenAPI
Database Schema
Table: locations
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_unit_id UUID NOT NULL REFERENCES organization_units(id),
op_unit_id UUID REFERENCES operational_units(id),
location_type_key VARCHAR NOT NULL,
parent_location_id UUID REFERENCES locations(id),
name VARCHAR NOT NULL,
short_name VARCHAR,
slug VARCHAR,
code VARCHAR NOT NULL,
category_key VARCHAR NOT NULL,
path_ltree LTREE, -- PostgreSQL LTree untuk hierarchy
is_active BOOLEAN DEFAULT true,
address TEXT,
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
attributes JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
deleted_at TIMESTAMP,
-- Constraints
UNIQUE(org_unit_id, code), -- Code unique per organization unit
CHECK (latitude BETWEEN -90 AND 90),
CHECK (longitude BETWEEN -180 AND 180)
);
-- Indexes
CREATE INDEX idx_locations_org_unit ON locations(org_unit_id);
CREATE INDEX idx_locations_parent ON locations(parent_location_id);
CREATE INDEX idx_locations_path_ltree ON locations USING GIST(path_ltree);
CREATE INDEX idx_locations_type ON locations(location_type_key);
CREATE INDEX idx_locations_code ON locations(org_unit_id, code);
LTree Path Structure
LTree digunakan untuk menyimpan path hierarkis lokasi. Format:
- Root location:
0001(atau UUID segment pertama) - Child location:
0001.0002(parent path + UUID segment) - Deep hierarchy:
0001.0002.0003.0004
Keuntungan LTree:
- Query cepat untuk descendants/ancestors
- Built-in PostgreSQL support untuk hierarchy queries
- Index GIST untuk performa optimal
Module Structure
1. Commands (Write Operations)
Semua write operations menggunakan Command pattern:
CreateLocationCommand
- File:
commands/create-location.command.ts - Handler:
LocationsService.create() - Validations:
- Organization unit exists
- Location type exists
- Parent exists and active (if provided)
- Type hierarchy validation
- Code uniqueness within org_unit
- LTree Path: Auto-generated berdasarkan parent path
UpdateLocationCommand
- File:
commands/update-location.command.ts - Handler:
LocationsService.update() - Validations:
- Circular reference check
- Type hierarchy re-validation
- Code uniqueness check
- LTree Path: Recalculated jika parent berubah
MoveLocationCommand
- File:
commands/move-location.command.ts - Handler:
LocationsService.move() - Validations:
- New parent exists and active
- Cannot move to self
- Cannot move to descendant
- Type hierarchy validation
- LTree Path: Recalculated untuk location dan semua descendants
ToggleStatusCommand
- File:
commands/toggle-status.command.ts - Handler:
LocationsService.toggleStatus() - Validations:
- Cannot deactivate if has active children
- Action: Toggle
is_activefield
SoftDeleteLocationCommand
- File:
commands/soft-delete-location.command.ts - Handler:
LocationsService.remove() - Validations:
- Cannot delete if has active children
- Cannot delete if has inventory items
- Action: Set
is_active = false
HardDeleteLocationCommand
- File:
commands/hard-delete-location.command.ts - Handler:
LocationsService.hardDelete() - Validations:
- Cannot delete if has any children
- Must be soft deleted first
- Action: Permanent delete dari database
2. Queries (Read Operations)
Semua read operations menggunakan Query pattern:
GetLocationsQuery
- File:
queries/get-locations.query.ts - Handler:
LocationsService.findAll() - Features:
- Filtering (org_unit, type, category, parent, status)
- Search (name, code, short_name)
- Pagination
- Sorting
- Tree structure mode (
table_tree=1)
GetLocationByIdQuery
- File:
queries/get-location-by-id.query.ts - Handler:
LocationsService.findOne() - Returns: Full location details dengan relations
GetLocationChildrenQuery
- File:
queries/get-location-children.query.ts - Handler:
LocationsService.getChildren() - Returns: Direct children only (tidak recursive)
GetLocationParentsQuery
- File:
queries/get-location-parents.query.ts - Handler:
LocationsService.getParents() - Returns: All parents dari root sampai direct parent
SearchLocationQuery
- File:
queries/search-location.query.ts - Handler:
LocationsService.search() - Search Fields: name, code, short_name
- Returns: Quick search results dengan limit
GetLocationUsageQuery
- File:
queries/get-location-usage.query.ts - Handler:
LocationsService.getUsage() - Returns: Statistics (inventory count, child count)
3. DTOs (Data Transfer Objects)
CreateLocationDto
- File:
dto/create-location.dto.ts - Required: org_unit_id, location_type_key, name, code, category_key, is_active
- Optional: op_unit_id, parent_location_id, short_name, slug, address, latitude, longitude, attributes
UpdateLocationDto
- File:
dto/update-location.dto.ts - All fields optional - Partial update support
QueryParamLocationListDto
- File:
dto/query-param-location-list.dto.ts - Query parameters untuk filtering, pagination, sorting
LocationDetailResponseDto
- File:
dto/location-response.dto.ts - Response format untuk detail location
LocationPaginatedResponseDto
- File:
dto/location-response.dto.ts - Response format untuk paginated list
LocationUsageResponseDto
- File:
dto/location-response.dto.ts - Response format untuk usage statistics
4. Validators
LocationValidator
- File:
validation/location.validator.ts - Validations:
- Type hierarchy check
- Circular reference check
- Parent validation
- Code uniqueness
UuidExistsConstraint
- File:
validation/uuid-exists.validator.ts - Custom validator untuk check UUID exists di table tertentu
- Usage:
@UuidExists('table_name', 'field_name?')
5. Mappers
LocationMapper
- File:
mappers/location.mapper.ts - Functions:
toResponseDto()- Map Prisma model ke response DTOtoTreeStructure()- Transform flat list ke tree structurebuildTree()- Recursive tree building
Service Layer
LocationsService
Service utama yang mengkoordinasikan semua operations.
Dependencies:
- PrismaService (database access)
- LocationValidator (validation logic)
- Commands & Queries handlers
Key Methods:
// Write operations
create(userId: string, tenantId: string, dto: CreateLocationDto)
update(userId: string, tenantId: string, id: string, dto: UpdateLocationDto)
move(userId: string, tenantId: string, id: string, newParentId: string)
toggleStatus(userId: string, tenantId: string, id: string)
remove(userId: string, tenantId: string, id: string)
hardDelete(userId: string, tenantId: string, id: string)
// Read operations
findAll(tenantId: string, query: QueryParamLocationListDto)
findOne(tenantId: string, id: string)
getChildren(tenantId: string, id: string)
getParents(tenantId: string, id: string)
search(tenantId: string, keyword: string, limit?: number)
getUsage(tenantId: string, id: string)
Controller Layer
LocationsController
REST API controller dengan decorators:
@Controller('locations')- Base route@UseGuards(AuthGuard)- Authentication guard@ApiTags('locations')- Swagger tag@ApiBearerAuth()- Swagger auth
Endpoints:
POST /- CreateGET /- List (dengan query params)GET /tree- Tree structureGET /search- Quick searchGET /:id- Get by IDGET /:id/children- Get childrenGET /:id/parents- Get parentsGET /:id/usage- Get usage statsPUT /:id- UpdatePATCH /:id/status- Toggle statusPOST /:id/move- Move locationDELETE /:id- Soft deleteDELETE /hard-delete/:id- Hard delete
LTree Path Management
Path Generation
Create Location:
- Jika root (parent_id = null): Generate path baru
0001 - Jika ada parent: Concatenate parent path + new UUID segment
- Parent path:
0001.0002 - New path:
0001.0002.0003
- Parent path:
Move Location:
- Validasi new parent
- Calculate new path berdasarkan new parent
- Update path untuk location
- Recalculate path untuk semua descendants menggunakan recursive query
LTree Queries
Get Children:
SELECT * FROM locations
WHERE path_ltree <@ (SELECT path_ltree FROM locations WHERE id = $1)
AND id != $1
AND path_ltree ~ (SELECT path_ltree::text || '.*{1}' FROM locations WHERE id = $1);
Get Parents:
SELECT * FROM locations
WHERE path_ltree @> (SELECT path_ltree FROM locations WHERE id = $1)
AND id != $1
ORDER BY nlevel(path_ltree);
Get Descendants:
SELECT * FROM locations
WHERE path_ltree <@ (SELECT path_ltree FROM locations WHERE id = $1)
AND id != $1;
Validation Rules
Type Hierarchy
Location types memiliki level hierarchy:
- Level 1: Warehouse
- Level 2: Storage Area
- Level 3: Shelf
- Level 4: Bin
Rule: Child type level MUST be higher than parent type level.
Implementation:
const parentType = await getLocationType(parent.location_type_key);
const childType = await getLocationType(dto.location_type_key);
if (childType.level <= parentType.level) {
throw new BadRequestException('Type hierarchy invalid');
}
Circular Reference Check
Self Reference:
if (locationId === parentId) {
throw new BadRequestException('Cannot set as own parent');
}
Descendant Check:
// Check if new parent is a descendant
const descendantPaths = await getDescendantPaths(locationId);
if (descendantPaths.includes(newParentPath)) {
throw new BadRequestException('Cannot set parent to descendant');
}
Code Uniqueness
Code harus unik dalam organization unit yang sama:
const existing = await prisma.location.findFirst({
where: {
org_unit_id: dto.org_unit_id,
code: dto.code,
id: { not: locationId }, // Exclude current location for update
},
});
if (existing) {
throw new ConflictException('Code already exists');
}
Error Handling
Error Response Format
{
success: false,
statusCode: number,
message: string,
reason: string, // Error code
details?: object,
path: string,
timestamp: string
}
Error Codes
location.not-foundlocation.parent-not-foundlocation.type-not-foundlocation.type-hierarchy-invalidlocation.circular-reference-selflocation.circular-reference-descendantlocation.has-active-childrenlocation.has-childrenlocation.code-duplicate
Testing
Unit Tests
Test untuk setiap command dan query handler:
- Validation logic
- Business rules
- Error cases
Integration Tests
Test untuk:
- API endpoints
- Database operations
- LTree path management
- Hierarchy operations
Test Data
Setup test data dengan hierarchy:
Warehouse A (root)
└── Storage Area A1
└── Shelf A1-1
└── Bin A1-1-1
Performance Considerations
Indexes
idx_locations_path_ltree- GIST index untuk LTree queriesidx_locations_org_unit- Filter by organization unitidx_locations_parent- Parent lookupsidx_locations_code- Code uniqueness check
Query Optimization
- Use
selectuntuk limit fields - Pagination untuk large datasets
- Tree mode tidak menggunakan pagination (return all)
- Use LTree operators untuk efficient hierarchy queries
Caching
Consider caching untuk:
- Location type metadata
- Organization unit lookups
- Frequently accessed locations
Security
Authentication
Semua endpoints protected dengan AuthGuard:
- Validates JWT token via lania-sso
- Extracts user info dan tenant ID
- Attaches user data ke request
Authorization
Tenant isolation:
- Semua queries filter by
tenant_id - Users hanya bisa access locations dalam tenant mereka
- Cross-tenant access prevented
Input Validation
- DTO validation dengan class-validator
- UUID format validation
- Type checking
- SQL injection prevention (Prisma parameterized queries)
Deployment
Environment Variables
Required:
AUTH_URL- lania-sso service URLAUTH_SECRET_KEY- Secret key untuk auth serviceMY_SECRET_KEY- Service secret key- Database connection string
Database Migrations
LTree extension harus di-enable:
CREATE EXTENSION IF NOT EXISTS ltree;
Module Registration
Module sudah terdaftar di app.module.ts:
imports: [
// ...
LocationsModule,
]
Future Improvements
-
Bulk Operations
- Bulk create locations
- Bulk update
- Bulk move
-
Advanced Filtering
- Filter by geographic bounds
- Filter by attributes
- Complex search queries
-
Audit Trail
- Track location changes
- History of moves
- Change logs
-
Performance
- Implement caching layer
- Optimize tree queries
- Add database connection pooling
-
Features
- Location templates
- Import/Export locations
- Location cloning
- Batch operations
Troubleshooting
Common Issues
LTree path not updating:
- Check database triggers
- Verify parent_id changes
- Check LTree extension enabled
Circular reference errors:
- Verify validation logic
- Check LTree path calculations
- Review move operations
Performance issues:
- Check indexes
- Review query patterns
- Consider pagination
- Monitor database performance