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
This commit is contained in:
oxpoe committed 2026-10-09 05:51:21 +07:00
commit eaf05f2811
280 files changed
+123975

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
'use strict';
/**
* registrationService.js — Perhitungan biaya awal pendaftaran (prorata, acuan tgl 5).
* biaya pasang + prorata paket − diskon voucher = total bayar awal.
*
* Aturan acuan tgl 5:
* - daftar SEBELUM tgl 5 → paket dihitung dari tgl daftar sampai tgl 5 BULAN INI
* - daftar tgl 5 atau SESUDAH → dihitung sampai tgl 5 BULAN DEPAN
*/
const { getSetting } = require('../config/settingsManager');
const voucherService = require('./voucherService');
function localParts(date) {
const tz = getSetting('timezone', 'Asia/Jakarta');
try {
const fmt = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' });
const o = {};
fmt.formatToParts(date).forEach(p => { o[p.type] = p.value; });
return { y: Number(o.year), m: Number(o.month), d: Number(o.day) };
} catch (e) {
return { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate() };
}
}
function pad(n) { return String(n).padStart(2, '0'); }
function computeBill(pkgPrice, opts = {}) {
const now = opts.now ? new Date(opts.now) : new Date();
const { y, m, d } = localParts(now);
const price = Math.max(0, Math.round(Number(pkgPrice) || 0));
const installFee = Math.max(0, Math.round(Number(opts.installFee != null ? opts.installFee : getSetting('registration_install_fee', 0)) || 0));
let dueY = y, dueM = m;
if (d >= 5) {
if (m === 12) { dueY = y + 1; dueM = 1; } else { dueM = m + 1; }
}
const start = new Date(y, m - 1, d);
const due = new Date(dueY, dueM - 1, 5);
const diffDays = Math.max(1, Math.round((due.getTime() - start.getTime()) / 86400000));
const daysInMonth = new Date(dueY, dueM, 0).getDate();
const costPerDay = price / daysInMonth;
const prorata = Math.round(diffDays * costPerDay);
const gross = prorata + installFee;
let discount = 0;
let voucher = null;
if (opts.voucherCode) {
const v = voucherService.getByCode(opts.voucherCode);
if (v) {
const chk = voucherService.validate(opts.voucherCode, gross);
if (chk.ok) { discount = chk.discount; voucher = v; }
}
}
const total = Math.max(0, gross - discount);
return {
price,
installFee,
startDate: `${y}-${pad(m)}-${pad(d)}`,
dueDate: `${dueY}-${pad(dueM)}-05`,
diffDays,
daysInMonth,
costPerDay: Math.round(costPerDay),
prorata,
gross,
discount,
total,
voucherCode: voucher ? voucher.code : '',
voucherValid: !!voucher
};
}
module.exports = { computeBill, localParts };