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
125 lines
4.2 KiB
JavaScript
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
|
|
};
|