Sales Order SPK & Automated Provisioning Lifecycle
Customer Onboarding, ODP Port Allocation & RADIUS Provisioning
Otomasi penerbitan Surat Perintah Kerja (SPK) pasang baru pelanggan, validasi KTP/NIK, reservasi port ODP selama 24 jam, dan pembuatan akun PPPoE/RADIUS otomatis di NestJS.
Diagram Alur Data & Arsitektur Sistem
Visual InteraktifAlur Penerbitan SPK Pasang Baru & Pre-Provisioning RADIUS
Penguncian port ODP atomik dengan auto-provisioning FreeRADIUS
Sales Portal
Input Data Prospek
NestJS SPK Engine
Order Verification
PostgreSQL DB
Atomic SPK & Port Tx
Redis Port Lock
TTL 24h Reservation
FreeRADIUS Server
Pre-provision PPPoE
Problem & Latar Belakang Kebutuhan
Pada operasional sales ISP konvensional, proses pengajuan pasang baru pelanggan dilakukan manual dengan form cetak atau spreadsheet. Hal ini sering menimbulkan insiden double-booking port splitter ODP yang sama saat teknisi tiba di lokasi, serta keterlambatan aktivasi internet karena tim admin NOC harus membuat profil PPPoE di MikroTik/FreeRADIUS secara manual satu per satu.
Implementasi ServiceOrderProvisioningService di NestJS
Service NestJS memvalidasi data prospek dan titik koordinat geospasial, kemudian menjalankan transaksi atomik: mengunci nomor port pada tabel odp_nodes, menerbitkan record SPK bernomor urut terisolasi (contoh: SPK-2026-JKT-091), dan memicu background worker untuk mengonfigurasi profil bandwidth PPPoE di server FreeRADIUS / MikroTik via UDP CoA / API.
Aspek Skema Relasional PostgreSQL & State Machine SPK
Tabel PostgreSQL service_orders menyimpan referensi relasional ke calon pelanggan, titik koordinat GPS, paket langganan, dan foreign key odp_node_id. State machine transisi pesanan: DRAFT -> SPK_ISSUED -> ODP_RESERVED -> FIELD_INSTALLED -> ACTIVATED.
Keamanan, Idempotensi & Kepatuhan SLA
Reservasi port ODP dikunci di Redis dengan TTL 86.400 detik (24 jam) menggunakan key lock:odp:{id}:port:{index}. Jika instalasi fisik batal atau melewati batas waktu 24 jam, port otomatis rilis kembali ke pool ketersediaan tanpa intervensi manual.
// Service Order SPK & Automated Provisioning Service di NestJS
@Injectable()
export class ServiceOrderProvisioningService {
constructor(
@InjectDataSource() private readonly dataSource: DataSource,
private readonly redisService: RedisService,
private readonly radiusClient: RadiusClientService,
) {}
async createServiceOrder(dto: CreateOrderSpkDto): Promise<ServiceOrderResponse> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction('SERIALIZABLE');
try {
// 1. Verifikasi ketersediaan port ODP dengan pessimistic write lock
const odp = await queryRunner.manager.createQueryBuilder(OdpNode, 'odp')
.setLock('pessimistic_write')
.where('odp.id = :id', { id: dto.odpId })
.getOne();
if (!odp || odp.usedPorts >= odp.totalPorts) {
throw new ConflictException('Port splitter ODP telah penuh.');
}
// 2. Lock port ODP di Redis (TTL 24 Jam)
const lockKey = `lock:odp:${odp.id}:port:${odp.usedPorts + 1}`;
const locked = await this.redisService.set(lockKey, dto.customerNik, 'EX', 86400, 'NX');
if (!locked) {
throw new ConflictException('Port sedang dalam reservasi aktif SPK lain.');
}
// 3. Terbitkan record SPK bernomor urut resmi
const spkNumber = `SPK-${new Date().getFullYear()}-${dto.branchCode}-${Math.floor(1000 + Math.random() * 9000)}`;
const order = queryRunner.manager.create(ServiceOrder, {
spkNumber,
customerName: dto.customerName,
customerNik: dto.customerNik,
customerPhone: dto.customerPhone,
packageCode: dto.packageCode,
odpId: odp.id,
portIndex: odp.usedPorts + 1,
status: 'SPK_ISSUED',
createdAt: new Date(),
});
await queryRunner.manager.save(order);
// Increment port used count
await queryRunner.manager.update(OdpNode, odp.id, {
usedPorts: odp.usedPorts + 1,
});
await queryRunner.commitTransaction();
// 4. Pre-provision akun PPPoE di FreeRADIUS
await this.radiusClient.createSubscriberAccount({
username: `user_${dto.customerPhone.slice(-6)}`,
profileSpeed: dto.packageCode,
nasIp: odp.nasIp,
});
return { spkNumber, status: 'SPK_ISSUED', reservedPort: odp.usedPorts + 1 };
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
}Live Simulator & Execution Sandbox
PostGIS GiST R-Tree ST_DWithin query executed in 3.8ms. Matched ODP-FATMAWATI-42 (84.2m).
Estimated optical attenuation: -17.4 dBm. Port 4 of 8 available. Redis lock acquired with 24h TTL.
HMAC-SHA256 verified. Redis Redlock acquired. Invoice INV-2026-0982 settled with zero ledger variance.
ST_DWithin R-Tree matched ODP in 4.1ms (dist: 84.2m). Optical loss -17.4 dBm. Splitter port locked with 24h Redis TTL.
Eksplorasi Fitur Terkait Lainnya
Lihat Semua Fitur →PostGIS Spatial R-Tree & ODP Fiber Allocation
Validasi geospasial real-time titik koordinat pelanggan, kalkulasi jarak kabel drop optik, estimasi degradasi redaman (dBm), dan auto-lock port splitter ODP tanpa full-table scan.
Multi-Tenant Branch & Multi-Level Approval Engine
Pemisahan data operasional antar cabang regional ISP berbasis Role-Based Access Control (RBAC) & Scoped Tenant Guard serta pipeline persetujuan multi-level otomatis (Sales -> NOC Kelayakan -> Finance Margin -> Dispatch).
Multi-Gateway Webhook & Double-Entry Reconcile
Pengolahan webhook multi-gateway (Midtrans, Xendit, Bank Direct VA) dengan transactional outbox pattern dan double-entry ledger untuk rekonsiliasi bebas selisih.
SLA Ticketing & CS Incident Assignment Management
Sistem manajemen tiket gangguan pelanggan berbasis web portal yang diinput oleh tim Customer Service (CS), dikelompokkan berdasarkan kategori insiden, lalu di-assign ke tim teknis terkait dengan pelacakan batas waktu resolusi SLA.