fix(qr-custom): ikon menu + disable sampai QRIS statis siap
All checks were successful
CI / test (push) Successful in 1m43s

- icon bi-qr-code
- menu disembunyikan bila QRIS belum aktif/payload kosong
- form & POST ditolak, tidak membuat data tanpa QR
- versi 0.6.1
This commit is contained in:
oxpoe
2026-11-06 04:38:12 +07:00
parent bf25260d4c
commit fd46731a53
8 changed files with 40 additions and 7 deletions

View File

@@ -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

View File

@@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.6.0_qr--custom-ADFF2F" alt="Versi 0.6.0 (qr-custom)">
<img src="https://img.shields.io/badge/versi-0.6.1_qr--gate-ADFF2F" alt="Versi 0.6.1 (qr-custom-gate)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>

View File

@@ -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": {

View File

@@ -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()
});
});

View File

@@ -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,

View File

@@ -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)) {

View File

@@ -1 +1 @@
0.6.0
0.6.1

View File

@@ -23,10 +23,11 @@
</div>
<% } %>
<% if (typeof hasQrisPayload !== 'undefined' && !hasQrisPayload) { %>
<div class="alert alert-d"><i class="bi bi-exclamation-triangle"></i> QRIS statis belum diatur. Isi <b>QRIS static payload</b> di Pengaturan agar QR bisa dibuat.</div>
<% if (typeof ready !== 'undefined' && !ready) { %>
<div class="alert alert-d"><i class="bi bi-exclamation-triangle"></i> <b>QR-Custom Payment dinonaktifkan.</b> Aktifkan <b>QRIS Statis (Semi-Otomatis)</b> dan isi <b>QRIS static payload</b> di <a href="/admin/settings" style="color:inherit;text-decoration:underline">Pengaturan</a> terlebih dahulu.</div>
<% } %>
<% if (typeof ready === 'undefined' || ready) { %>
<div class="card mb16">
<div class="card-hd"><h6><i class="bi bi-plus-circle"></i> Buat QR Nominal Custom</h6></div>
<div class="card-bd">
@@ -43,6 +44,7 @@
</form>
</div>
</div>
<% } %>
<div class="form-row mb16">
<div class="fg"><div class="card"><div class="card-bd"><div class="muted" style="font-size:12px">Pembayaran masuk</div><div style="font-size:22px;font-weight:800"><%= stats.paidCount %></div><div class="muted" style="font-size:11px">Rp <%= Number(stats.paidTotal||0).toLocaleString('id-ID') %></div></div></div></div>