fix(qr-custom): ikon menu + disable sampai QRIS statis siap
All checks were successful
CI / test (push) Successful in 1m43s
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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.0
|
||||
0.6.1
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user