[ND-319/LNSW.34/2026] Penyesuaian Durasi Submission Cooldown 60 Detik, Auto-Release Lock Redis, dan Null-Safety Data NIB Pengajuan PAB
Modul: SIPT/PAB | Tipe: Bugfix | Stage Saat Ini:
DEVELOPMENT| Tanggal Dokumen: 2026-09-15
DEVELOPMENT STAGE
Dokumen ini sedang berada dalam tahap DEVELOPMENT. Perubahan logika backend masih aktif di lingkungan pengembangan (Development).
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
ND-319/LNSW.34/2026Sumber Instruksi: Nota Dinas Resmi ND-319/LNSW.34/2026
Dokumen Referensi / Lampiran: Permohonan Perbaikan Bug pada Aplikasi PAB.pdf (tersimpan pada direktori attachments)
Task / Dependensi Terkait (Cross-Service): —
Modul Terkait:
SIPT/PABService / Repository:
sipt-backendPIC Pengembang: Hermansyah Efendi, Muhamad Firly Ramadan
Ringkasan Non-Teknis (Executive Summary): Aplikasi PAB sebelumnya memiliki waktu tunggu pengiriman ulang (cooldown) hingga 5 menit yang menyulitkan pelaku usaha ketika terjadi kegagalan transmisi. Selain itu, sistem kerap mengalami error fatal (crash) saat memproses data pengajuan yang tidak mencantumkan Nomor Induk Berusaha (NIB) milik ekspedisi atau penerima.
Melalui perbaikan ini:
- Waktu tunggu submission dipangkas menjadi 60 detik (1 menit), dan kunci antrean otomatis dibebaskan seketika setelah server selesai memproses data tanpa menahan pengguna.
- Sistem kebal terhadap nilai NIB kosong (
null/undefined) serta mendukung input khusus karakter'0', memastikan pengajuan tidak gagal validasi di tengah jalan. - Pada integrasi Kemendag, sistem secara defensif menerapkan fallback otomatis ke string
"0"ketika atribut NIB ekspedisi (ekspedisi.nib) atau NIB penerima (penerima.nib_penerima) bernilainull,undefined, string kosong (""), atau hanya berupa spasi (" "), menjamin kesesuaian skema payload hilir.
Latar Belakang & Ringkasan Kebutuhan: Berdasarkan evaluasi operasional pada sistem PAB (Pemberitahuan Arus Barang) dan laporan bug pada Nota Dinas ND-319/LNSW.34/2026:
- Durasi submission cooldown pengajuan PAB sebelumnya bernilai 300 detik (5 menit) yang dinilai terlalu lama saat terjadi kegagalan jaringan atau kebutuhan retry, serta mekanisme lock Redis sebelumnya tidak dilepas ketika request selesai lebih awal atau terjadi connection abort.
- Terjadi runtime crash
TypeError: Cannot read properties of undefined (reading 'trim')pada integrasi Kemendag dan skema validasi Zod ketika memproses data pelaku usaha yang memiliki atribut NIB atau NIB Penerima kosong (null/undefined). Proses bisnis PAB mengizinkan data NIB bernilai kosong atau bernilai'0', sehingga diperlukan penanganan null-safety komprehensif, fallback string"0", dan schema refinement yang fleksibel.
Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Null-safety guard pada integrasi Kemendag (
kemendag.api.js) dan controller pembatalan PAB (newsend_sipt_controller.js) - [x] Peningkatan helper validasi Zod (
zod.js) untuk mendukung opsiwhitelistdan penanganannullishsecara aman - [x] Pembaruan skema validasi
pelaku_usaha.validation.jsdengan mengizinkan nilai whitelist'0'untuk NIB Penerima - [x] Intersepsi
res.endpadasubmission_cooldown.jsuntuk memastikan release lock Redis selalu dieksekusi begitu request selesai - [x] Reduksi durasi cooldown pengajuan PAB dari 300 detik (5 menit) menjadi 60 detik (1 menit)
- [x] Pembersihan logika
releaseSubmissionLockredundan padakemendag-axios-interceptor.js - [x] Fallback otomatis ke string
"0"saat NIB ekspedisi dan NIB penerima bernilainull,undefined, atau string kosong pada integrasi Kemendag (kemendag.api.js) - [x] Penambahan unit test suite Kemendag API untuk verifikasi fallback
"0"pada skenario null, undefined, dan whitespace-only (tests/api/kemendag.api.test.js)
- [x] Null-safety guard pada integrasi Kemendag (
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-08-07 17:16 | Hermansyah Efendi, Muhamad Firly Ramadan | DEPLOYED | Branch: development (Commit: 761ad750 .. f5a4673) |
| 2 | Staging (UAT) | — | — | PENDING | — |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-08-24 | Development | Inisialisasi draft awal audit log (commit ebd871dc) | Hermansyah Efendi, Muhamad Firly Ramadan |
| 2026-09-15 | Development | Sinkronisasi multi-commit trail lengkap (761ad750 .. 2fcb6e99), koreksi rute kontrak API riil (receivePelaporan & cancellation), pemutakhiran cuplikan kode real Zod helper, dan standarisasi QA Playbook | Hermansyah Efendi, Muhamad Firly Ramadan |
| 2026-09-15 | Development | Penambahan fallback default string "0" saat NIB ekspedisi/penerima bernilai null, undefined, atau whitespace-only (commit 218b653 & f5a4673) serta penambahan unit test Kemendag API | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Optimasi Middleware Cooldown & Lifecycle Lock Redis (
app/middlewares/submission_cooldown.js):- Mengurangi konstanta
COOLDOWN_SECONDSdari 300 detik (5 menit) menjadi 60 detik (1 menit). - Mengimplementasikan monkey-patching pada lifecycle response Express (
res.end) agar otomatis memicu fungsireleaseLock()yang mengeksekusiredisClient.del(cacheKey)saat request selesai (finish/closemaupun error). Hal ini menjamin lock hanya menahan konkurensi selama proses berlangsung dan langsung dibebaskan pasca-eksekusi tanpa menunggu TTL habis.
- Mengurangi konstanta
- Pembersihan Logika Interceptor Manual Release (
app/helpers/kemendag-axios-interceptor.js):- Menghapus logika pelepasan manual lock Redis (
releaseSubmissionLock) yang sebelumnya tersebar di axios interceptor karena lifecycle lock kini terpusat secara deterministik pada middleware Expresssubmission_cooldown.js.
- Menghapus logika pelepasan manual lock Redis (
- Helper Validasi Zod & Whitelist Extension (
app/helpers/zod.js):- Menambahkan parameter opsi
whitelistpada fungsi helpercreateDigitStringSchema. - Menggunakan
z.unionantara schema digit regex denganz.enum(whitelist)serta penanganan.nullish()dan transformasi string kosong ("" -> null) agar input bernilai khusus tidak tertolak validator.
- Menambahkan parameter opsi
- Pembaruan Skema Validasi Pelaku Usaha (
app/validations/pelaku_usaha.validation.js):- Mengonfigurasi properti
nib_penerimaagar mengizinkan nilai'0'via opsiwhitelist: ['0']tanpa melanggar ketentuan digit length.
- Mengonfigurasi properti
- Null-Safety & Fallback String "0" Integrasi Kemendag (
app/api/kemendag.api.js):- Menerapkan optional chaining dan operator fallback string
"0":ekspedisi.nib?.trim() || "0"danpenerima.nib_penerima?.trim() || "0". - Mengeliminasi fatal crash
TypeError: Cannot read properties of undefined (reading 'trim')sekaligus memastikan jika nilai NIB tidak diisi (undefined), bernilainull, string kosong (""), atau hanya berupa spasi (" "), payload downstream Kemendag selalu menerima nilai"0"yang sah menurut kontrak integrasi.
- Menerapkan optional chaining dan operator fallback string
- Null-Safety Penanganan Pembatalan Pengajuan (
app/controllers/newsend_sipt_controller.js):- Menambahkan optional chaining pada parameter pembatalan pengajuan PAB untuk menjamin keandalan payload sebelum diteruskan ke service layer.
- Optimasi Middleware Cooldown & Lifecycle Lock Redis (
Perubahan Database (DDL / DML):
sql-- Tidak ada perubahan skema database (DDL/DML) untuk task ini. -- Perubahan difokuskan pada middleware submission cooldown (Redis lock), Zod schema helper, dan defensive null-safety logic.Kontrak API & Response Payload:
POST/receivePelaporan- Deskripsi: Endpoint penerimaan dan pengajuan pelaporan PAB ke instansi terkait / Kemendag.
- Autentikasi / Guard:
Bearer Token(auth.doAuth,submissionCooldown("receivePelaporan")) - Request Header:
Authorization:Bearer <token>Content-Type:application/json
- Request Body:json
{ "nomor_layanan": "PAB-2026-0001", "pelaku_usaha": { "ekspedisi": { "npwp": "012345678901234", "nib": null, "nama": "PT Ekspedisi Logistik Nusantara" }, "penerima": { "npwp_penerima": "987654321098765", "nib_penerima": "0", "nama_penerima": "Toko Sejahtera" } } } - Response Payload (
200 OK):json{ "data": { "nomor_layanan": "PAB-2026-0001", "status": "TERKIRIM" }, "message": "Pelaporan PAB berhasil dikirim", "statusCode": 200, "success": true } - Response Error (
429 Too Many Requests— Concurrent Submission):json{ "data": null, "message": "Pengajuan untuk nomor layanan PAB-2026-0001 sudah dikirim. Silahkan coba lagi dalam 1 menit.", "statusCode": 429, "success": false, "errors": [] }
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
sipt-backend - Branch Utama:
development - Merge Request / PR:
—
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
761ad750989a4052c4fffa3dff9bf68704b62d36 | development | Hermansyah Efendi | 2026-07-02 14:15 | fix(validation): prevent TypeError on trimming null or undefined values |
12a48621cd4121ccb2553786850c640b542d1eb8 | development | Hermansyah Efendi | 2026-07-02 17:44 | feat(validation): support whitelist option in digit string schema |
adcf42a09ce39c3a45c552559f79e390e6c84688 | development | Hermansyah Efendi | 2026-07-06 16:40 | fix(cooldown): ensure lock is released on request completion |
ebd871dc5d343ae7ef7983e659c8ef773b18c434 | development | Muhamad Firly Ramadan | 2026-08-07 17:14 | feat(middleware): update cooldown second to 60s (1m) |
2fcb6e99979f31cff9b95bce7447a809c306a2e4 | development | Muhamad Firly Ramadan | 2026-08-07 17:16 | chore(changelog): write cooldown middleware update |
218b65387118beb5ea99457030d5187b8f479e4a | development | Hermansyah Efendi | 2026-09-15 10:42 | test(kemendag): add test cases for null and undefined nib values |
f5a46737771c20ee6c2022b318cf8d19bf152db8 | development | Hermansyah Efendi | 2026-09-15 11:03 | fix(kemendag): fallback empty ekspedisi and penerima nib to "0" |
Cuplikan Kode / Implementasi Inti
// File: app/middlewares/submission_cooldown.js
const COOLDOWN_SECONDS = 60; // 1 menit
export const submissionCooldown = (action) => {
return async (req, res, next) => {
try {
const nomorLayanan = req.nomorLayanan;
if (!nomorLayanan) return next();
const cacheKey = CacheKeys.SUBMISSION_COOLDOWN(action, nomorLayanan);
const result = await redisClient.set(cacheKey, Date.now().toString(), "EX", COOLDOWN_SECONDS, "NX");
if (result === null) {
const ttl = await redisClient.ttl(cacheKey);
const minutesLeft = Math.ceil(ttl / 60);
throw new TooManyRequestsError(
`Pengajuan untuk nomor layanan ${nomorLayanan} sudah dikirim. Silahkan coba lagi dalam ${minutesLeft} menit jika status masih ${statusSipt.draft} atau ${statusSipt.gagal_kirim}.`
);
}
// Intersepsi res.end (monkey patching) untuk memastikan lock selalu dibebaskan saat
// backend selesai memproses request, baik sukses maupun error.
let released = false;
const releaseLock = async () => {
if (released) return;
released = true;
try {
await redisClient.del(cacheKey);
logger.debug(
{ nomor_layanan: nomorLayanan, action },
"Released submission cooldown lock on request completion"
);
} catch (err) {
logger.error(
{ err, nomor_layanan: nomorLayanan, action },
"Failed to release submission cooldown lock on request completion"
);
}
};
const originalEnd = res.end;
res.end = function (...args) {
releaseLock();
return originalEnd.apply(this, args);
};
next();
} catch (error) {
next(error);
}
};
};// File: app/helpers/zod.js
export const createDigitStringSchema = (field, { required = true, maxLength, minLength, whitelist = [] } = {}) => {
const digitRegex = /^[0-9]+$/;
if (!required) {
let innerSchema = z
.string()
.regex(digitRegex, { message: `${field} hanya boleh berisi angka` })
.max(maxLength ?? 255, { message: `${field} maksimal ${maxLength ?? 255} karakter` })
.min(minLength ?? 1, { message: `${field} minimal ${minLength ?? 1} karakter` });
if (whitelist.length > 0) {
innerSchema = z.union([
innerSchema,
z.enum(whitelist, { errorMap: () => ({ message: `${field} tidak valid` }) })
]);
}
return z
.string()
.nullish()
.transform((val) => (val === "" ? null : val))
.pipe(innerSchema.nullish());
}
// ...
};// File: app/api/kemendag.api.js (Null-Safety & Fallback String "0" NIB)
ekspedisi: {
npwp: ekspedisi.npwp,
nib: ekspedisi.nib?.trim() || "0",
tipe_perusahaan: ekspedisi.tipe_perusahaan,
nama: ekspedisi.nama,
surat_kuasa: ekspedisi.surat_kuasa,
// ...
},
penerima: {
jenis_tujuan: penerima.jenis_tujuan,
npwp_penerima: penerima.npwp_penerima,
nib_penerima: penerima.nib_penerima?.trim() || "0",
nama_penerima: penerima.nama_penerima,
// ...
}5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite Files:
tests/middlewares/submission_cooldown.test.jstests/validations/pelaku_usaha.validation.test.jstests/api/kemendag.api.test.js
- Status Eksekusi CLI:
PASSED
// File: tests/api/kemendag.api.test.js (Verifikasi Fallback "0" pada Kemendag API)
it('handles null nib in ekspedisi and penerima by defaulting to "0"', async () => {
const inputData = getBasePelaporanInput();
inputData.ekspedisi.nib = null;
inputData.penerima.nib_penerima = null;
await kemendagApi.sendPelaporanPab(inputData);
expect(mockKemendagAxios.post).toHaveBeenCalledWith('/receivePelaporan', expect.objectContaining({
ekspedisi: expect.objectContaining({ nib: '0' }),
penerima: expect.objectContaining({ nib_penerima: '0' }),
}));
});
it('handles whitespace-only nib in ekspedisi and penerima by defaulting to "0"', async () => {
const inputData = getBasePelaporanInput();
inputData.ekspedisi.nib = ' ';
inputData.penerima.nib_penerima = '';
await kemendagApi.sendPelaporanPab(inputData);
expect(mockKemendagAxios.post).toHaveBeenCalledWith('/receivePelaporan', expect.objectContaining({
ekspedisi: expect.objectContaining({ nib: '0' }),
penerima: expect.objectContaining({ nib_penerima: '0' }),
}));
});// File: tests/middlewares/submission_cooldown.test.js
it("should allow request and release lock when res.end is called", async () => {
const middleware = submissionCooldown("test_action");
await middleware(req, res, next);
expect(next).toHaveBeenCalled();
expect(redisClient.set).toHaveBeenCalledWith("cooldown:test_action:NOMOR123", "locked", "EX", 60);
res.end();
expect(redisClient.del).toHaveBeenCalledWith("cooldown:test_action:NOMOR123");
}); RUN v4.1.11 D:/WORK/LNSW/sipt-backend
✓ tests/validations/pelaku_usaha.validation.test.js (4 tests) 9ms
✓ tests/middlewares/submission_cooldown.test.js (3 tests) 7ms
Test Files 2 passed (2)
Tests 7 passed (7)
Start at 09:20:20
Duration 793ms (transform 336ms, setup 0ms, import 673ms, tests 16ms, environment 0ms)B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook)
Diisi saat development sebagai panduan eksekusi pengujian QA, kemudian dilengkapi bukti response JSON saat verifikasi di Staging.
- Cakupan / Surface Pengujian:
Full E2E (UI + Backend) & API / Postman - Kebutuhan Akses / Role Akun:
Akun Pelaku Usaha PAB & Internal INSW - Hasil Pengujian UAT:
PENDING - Catatan QA: —
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Verifikasi Null-Safety NIB Penerima & Ekspedisi (Happy Path):
- Pre-kondisi: Form pengajuan PAB siap disubmit dengan data NIB Penerima bernilai
0atau dibiarkan kosong (null). - Trigger / Request: Submit payload pengajuan via UI atau
POST /api/v1/newsend_sipt/receivePelaporan. - Ekspektasi Response: HTTP
200 OK, validasi lolos tanpa errorTypeError: Cannot read properties of undefined (reading 'trim'). - Verifikasi Database / Log: Log service menunjukkan pengiriman payload ke Kemendag sukses tanpa warning trim undefined.
- Pre-kondisi: Form pengajuan PAB siap disubmit dengan data NIB Penerima bernilai
Skenario 2 — Verifikasi Submission Cooldown & Lock Auto-Release (Edge Case):
- Pre-kondisi: Request pengajuan pertama selesai diproses atau mengalami kegagalan downstream.
- Trigger / Request: Segera kirim request kedua dengan nomor layanan yang sama setelah response pertama selesai.
- Ekspektasi Response: Request tidak tertahan cooldown jika request pertama telah selesai tereksekusi (lock dibebaskan oleh
res.end). Jika request kedua dikirim paralel saat request pertama masih pending, ekspektasi response adalah HTTP429 Too Many Requestsdengan batas TTL maksimum 60 detik. - Verifikasi Database / Log: Key Redis
cooldown:receivePelaporan:{nomor_layanan}terhapus segera setelah request selesai.
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}