Skip to main content

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_active field

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 DTO
    • toTreeStructure() - Transform flat list ke tree structure
    • buildTree() - 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 / - Create
  • GET / - List (dengan query params)
  • GET /tree - Tree structure
  • GET /search - Quick search
  • GET /:id - Get by ID
  • GET /:id/children - Get children
  • GET /:id/parents - Get parents
  • GET /:id/usage - Get usage stats
  • PUT /:id - Update
  • PATCH /:id/status - Toggle status
  • POST /:id/move - Move location
  • DELETE /:id - Soft delete
  • DELETE /hard-delete/:id - Hard delete

LTree Path Management

Path Generation

Create Location:

  1. Jika root (parent_id = null): Generate path baru 0001
  2. Jika ada parent: Concatenate parent path + new UUID segment
    • Parent path: 0001.0002
    • New path: 0001.0002.0003

Move Location:

  1. Validasi new parent
  2. Calculate new path berdasarkan new parent
  3. Update path untuk location
  4. 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-found
  • location.parent-not-found
  • location.type-not-found
  • location.type-hierarchy-invalid
  • location.circular-reference-self
  • location.circular-reference-descendant
  • location.has-active-children
  • location.has-children
  • location.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 queries
  • idx_locations_org_unit - Filter by organization unit
  • idx_locations_parent - Parent lookups
  • idx_locations_code - Code uniqueness check

Query Optimization

  • Use select untuk 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 URL
  • AUTH_SECRET_KEY - Secret key untuk auth service
  • MY_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

  1. Bulk Operations

    • Bulk create locations
    • Bulk update
    • Bulk move
  2. Advanced Filtering

    • Filter by geographic bounds
    • Filter by attributes
    • Complex search queries
  3. Audit Trail

    • Track location changes
    • History of moves
    • Change logs
  4. Performance

    • Implement caching layer
    • Optimize tree queries
    • Add database connection pooling
  5. 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

References