Files
open-isp/services/odpService.js
T
oxpoe eaf05f2811 OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor,
admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS.

Kredit & lisensi (ISC):
- Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak
  atas base kode dan repo yang bermanfaat.
- Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder.
- Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
2026-10-09 05:51:21 +07:00

125 lines
4.2 KiB
JavaScript

const db = require('../config/database');
/**
* ODP SERVICE
* Mengelola data Optical Distribution Point (ODP) + skema port.
* - port_capacity = jumlah maksimal port pada ODP.
* - customers.pon_port = nomor port ODP yang dipakai pelanggan.
*/
function getAllOdps() {
return db.prepare(`
SELECT o.*, olt.name as olt_name
FROM odps o
LEFT JOIN olts olt ON o.olt_id = olt.id
ORDER BY o.name ASC
`).all();
}
function getOdpById(id) {
return db.prepare('SELECT * FROM odps WHERE id = ?').get(id);
}
function parseCapacity(v) {
const n = parseInt(v, 10);
return (Number.isFinite(n) && n > 0) ? n : 16;
}
function createOdp(data) {
const capacity = parseCapacity(data.port_capacity);
const stmt = db.prepare(`
INSERT INTO odps (name, olt_id, pon_port, port_capacity, lat, lng, description)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
return stmt.run(
data.name,
data.olt_id ? parseInt(data.olt_id) : null,
data.pon_port || '',
capacity,
data.lat || '',
data.lng || '',
data.description || ''
);
}
function updateOdp(id, data) {
const capacity = parseCapacity(data.port_capacity);
const usage = getOdpPortUsage(id);
if (usage && capacity < usage.usedCount) {
throw new Error('Kapasitas port (' + capacity + ') lebih kecil dari jumlah port yang sudah terpakai (' + usage.usedCount + ').');
}
const stmt = db.prepare(`
UPDATE odps
SET name = ?, olt_id = ?, pon_port = ?, port_capacity = ?, lat = ?, lng = ?, description = ?
WHERE id = ?
`);
return stmt.run(
data.name,
data.olt_id ? parseInt(data.olt_id) : null,
data.pon_port || '',
capacity,
data.lat || '',
data.lng || '',
data.description || '',
id
);
}
function deleteOdp(id) {
return db.prepare('DELETE FROM odps WHERE id = ?').run(id);
}
function getOdpPortUsage(odpId) {
const odp = getOdpById(odpId);
if (!odp) return null;
const usedRaw = db.prepare("SELECT pon_port, name, pppoe_username FROM customers WHERE odp_id = ? AND pon_port IS NOT NULL AND TRIM(pon_port) != ''").all(odpId);
const usedBy = {};
for (const r of usedRaw) {
const p = String(r.pon_port).trim();
if (p && usedBy[p] === undefined) usedBy[p] = { name: r.name || '', pppoe: String(r.pppoe_username || '').toLowerCase() };
}
const usedPorts = Object.keys(usedBy).sort((a, b) => a.localeCompare(b, 'id-ID', { numeric: true }));
const capacity = parseCapacity(odp.port_capacity);
const usedCount = usedPorts.length;
const remaining = Math.max(0, capacity - usedCount);
return { odpId: Number(odpId), capacity, usedCount, remaining, usedPorts, usedBy };
}
/**
* Validasi penempatan port pelanggan pada sebuah ODP.
* - Port tidak boleh melebihi kapasitas (maksimal port) ODP.
* - Port tidak boleh dipakai pelanggan lain pada ODP yang sama.
* Nilai non-numerik (data lama) dilewatkan agar tetap kompatibel.
*/
function validateOdpPort(odpId, ponPort, excludeCustomerId) {
if (odpId === undefined || odpId === null || String(odpId).trim() === '') return;
const portStr = String(ponPort === undefined || ponPort === null ? '' : ponPort).trim();
if (!portStr) return;
if (!/^\d+$/.test(portStr)) return;
const odp = getOdpById(Number(odpId));
if (!odp) throw new Error('ODP tidak ditemukan.');
const capacity = parseCapacity(odp.port_capacity);
const portNo = parseInt(portStr, 10);
if (portNo < 1 || portNo > capacity) {
throw new Error('Port ODP ' + portNo + ' melebihi kapasitas ODP "' + odp.name + '" (maksimal ' + capacity + ' port). Ubah kapasitas ODP bila perlu.');
}
const ex = (excludeCustomerId === undefined || excludeCustomerId === null || excludeCustomerId === '') ? null : Number(excludeCustomerId);
const dup = ex
? db.prepare("SELECT id, name FROM customers WHERE odp_id = ? AND TRIM(COALESCE(pon_port,'')) = ? AND id != ? LIMIT 1").get(Number(odpId), portStr, ex)
: db.prepare("SELECT id, name FROM customers WHERE odp_id = ? AND TRIM(COALESCE(pon_port,'')) = ? LIMIT 1").get(Number(odpId), portStr);
if (dup) throw new Error('Port ODP ' + portNo + ' sudah dipakai oleh pelanggan "' + dup.name + '".');
}
module.exports = {
getAllOdps,
getOdpById,
createOdp,
updateOdp,
deleteOdp,
getOdpPortUsage,
validateOdpPort
};