diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b00ffa..6017082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/). +## [0.6.1] — 2026-11-06 — "qr-custom-gate" + +### Diperbaiki +- Ikon menu **QR-Custom Payment** memakai ikon yang tersedia (`bi-qr-code`). +- Fitur QR-Custom **dinonaktifkan otomatis** bila QRIS Statis (Semi-Otomatis) belum aktif atau `qris_static_payload` kosong: + - menu disembunyikan, + - form pembuatan dinonaktifkan, + - request pembuatan ditolak (tidak ada data/list dibuat tanpa QR). + ## [0.6.0] — 2026-11-06 — "qr-custom" ### Ditambahkan diff --git a/README.md b/README.md index 42618aa..b3ee0a7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@
-
+
diff --git a/package.json b/package.json
index c7548c4..6989a76 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "open-isp",
- "version": "0.6.0",
+ "version": "0.6.1",
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
"main": "app-customer.js",
"scripts": {
diff --git a/routes/adminPortal.js b/routes/adminPortal.js
index d4cb9f4..e382112 100644
--- a/routes/adminPortal.js
+++ b/routes/adminPortal.js
@@ -4031,7 +4031,8 @@ router.get('/custom-qr', requireAdminSession, requireSidebarMenuAccess('custom_q
detail,
baseUrl,
durationMinutes: svc.DURATION_MINUTES,
- hasQrisPayload: !!String(settings.qris_static_payload || '').trim()
+ hasQrisPayload: !!String(settings.qris_static_payload || '').trim(),
+ ready: svc.isReady()
});
});
diff --git a/services/customQrService.js b/services/customQrService.js
index 6856750..5a3b375 100644
--- a/services/customQrService.js
+++ b/services/customQrService.js
@@ -7,9 +7,19 @@ const crypto = require('crypto');
const db = require('../config/database');
const { isQrisAmountTaken } = require('./invoiceQrisService');
const { logger } = require('../config/logger');
+const { getSetting } = require('../config/settingsManager');
const DURATION_MINUTES = 20;
+function isReady() {
+ try {
+ const enabled = getSetting('qris_static_enabled', true);
+ const off = enabled === false || enabled === 'false' || enabled === 0 || enabled === '0';
+ const payload = String(getSetting('qris_static_payload', '') || '').trim();
+ return !off && payload.length > 0;
+ } catch (e) { return false; }
+}
+
function nowIso() { return new Date().toISOString(); }
function sweepExpired() {
@@ -27,6 +37,7 @@ function isTaken(amount, excludeId) {
}
function create({ title, category, amount, note, createdBy }) {
+ if (!isReady()) throw new Error('QRIS Statis (Semi-Otomatis) belum aktif/berisi payload. Atur dulu di Pengaturan → QRIS.');
const base = Math.max(1, Math.floor(Number(amount) || 0));
if (!title || !String(title).trim()) throw new Error('Judul wajib diisi.');
if (!base) throw new Error('Nominal wajib diisi.');
@@ -161,6 +172,7 @@ function isExpired(row) {
module.exports = {
DURATION_MINUTES,
+ isReady,
create,
list,
stats,
diff --git a/services/sidebarMenuService.js b/services/sidebarMenuService.js
index eaed894..ee4bfe8 100644
--- a/services/sidebarMenuService.js
+++ b/services/sidebarMenuService.js
@@ -32,7 +32,7 @@ const MENU_DEFINITIONS = [
{ key: 'map', section: 'main', href: '/admin/map', icon: 'bi bi-map', labelKey: 'admin.nav.network_map', labelDefault: 'Peta Jaringan', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 4, activePages: ['map'] },
{ key: 'whatsapp', section: 'main', href: '/admin/whatsapp', icon: 'bi bi-whatsapp', labelKey: 'admin.nav.whatsapp', labelDefault: 'WhatsApp', roles: ['admin', 'cashier'], activePages: ['whatsapp','broadcast','whatsapp_live_chat','whatsapp_templates'] },
{ key: 'automation', section: 'main', href: '/admin/automation', icon: 'bi bi-robot', labelKey: 'admin.nav.automation', labelDefault: 'Automation', roles: ['admin'], activePages: ['automation'] },
- { key: 'custom_qr', section: 'main', href: '/admin/custom-qr', icon: 'bi bi-qr-code-simple', labelKey: 'admin.nav.custom_qr', labelDefault: 'QR-Custom Payment', roles: ['admin'], activePages: ['custom_qr'] },
+ { key: 'custom_qr', section: 'main', href: '/admin/custom-qr', icon: 'bi bi-qr-code', labelKey: 'admin.nav.custom_qr', labelDefault: 'QR-Custom Payment', roles: ['admin'], activePages: ['custom_qr'] },
{ key: 'grp_customers', section: 'billing', href: '/admin/customers', icon: 'bi bi-people', labelKey: 'admin.nav.customers', labelDefault: 'Pelanggan', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 2, activePages: ['customers', 'onu_stickers'], children: [
{ key: 'customers', section: 'billing', href: '/admin/customers', icon: 'bi bi-people', labelKey: 'admin.nav.customers_data', labelDefault: 'Data Pelanggan', roles: ['admin', 'cashier'], activePages: ['customers'] },
@@ -269,12 +269,21 @@ function enrichMenu(menu, states) {
return out;
}
+function isCustomQrReady() {
+ try {
+ const enabled = getSetting('qris_static_enabled', true);
+ const off = enabled === false || enabled === 'false' || enabled === 0 || enabled === '0';
+ return !off && String(getSetting('qris_static_payload', '') || '').trim().length > 0;
+ } catch (e) { return false; }
+}
+
function getSidebarSections(session) {
const states = getStoredMenuStates();
return SECTION_DEFINITIONS.map((section) => {
const items = MENU_DEFINITIONS
.filter((menu) => menu.section === section.key)
.filter((menu) => isMenuAllowedForSession(menu, session))
+ .filter((menu) => !(menu.key === 'custom_qr' && !isCustomQrReady()))
.map((menu) => {
const enriched = enrichMenu(menu, states);
if (Array.isArray(enriched.children)) {
diff --git a/version.txt b/version.txt
index 09a3acf..7ceb040 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.6.0
\ No newline at end of file
+0.6.1
\ No newline at end of file
diff --git a/views/admin/custom_qr.ejs b/views/admin/custom_qr.ejs
index abfdaab..40591fc 100644
--- a/views/admin/custom_qr.ejs
+++ b/views/admin/custom_qr.ejs
@@ -23,10 +23,11 @@
<% } %>
- <% if (typeof hasQrisPayload !== 'undefined' && !hasQrisPayload) { %>
-