[ND-274/LNSW.2/2026] Pembatasan dan Penonaktifan Otomatis Single Billing pada Layanan Kapal Ferry, Docking, dan Berkegiatan Tetap SSm Pengangkut
Modul: Pengangkut | Tipe: Change Request | Stage Saat Ini:
STAGING| Tanggal Dokumen: 2026-09-29
STAGING STAGE (UAT VERIFICATION)
Dokumen ini sedang berada dalam tahap STAGING. Perubahan logika backend dan penegakan mutual exclusivity Single Billing telah diterapkan di lingkungan Staging (UAT) untuk proses verifikasi QA.
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
ND-274/LNSW.2/2026Sumber Instruksi: Nota Dinas Resmi ND-274/LNSW.2/2026
Dokumen Referensi / Lampiran:
Task / Dependensi Terkait (Cross-Service): Tidak ada
Modul Terkait:
PengangkutService / Repository:
pengangkut-backendPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Menu Single Billing saat ini tetap muncul pada layanan kapal (Docking, Berkegiatan Tetap, dan Ferry) yang secara tagihan tidak dapat disatukan oleh fitur Single Billing PNBP. Kondisi ini mengharuskan adanya penonaktifan otomatis opsi Single Billing pada layanan tersebut serta penegakan validasi mutual exclusivity untuk mencegah konflik status billing antar-layanan kapal.
Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Skema enum
KapalJenisSinkarkesEnum('Ferry' | 'Non-Ferry') dan guard mutual exclusivityisSingleBillingpada SINKARKES - [x] Method
updateBillingFlagspadaagentServiceuntuk mutasi atomik flag billing dipengangkut.agents - [x] Guard validasi keberangkatan 2b (cegah penandaan docking/kegiatan tetap jika kedatangan ber-Single Billing) pada
shipService.upsertSupersetShip - [x] Logika auto-reset flag billing 2a pada Superset Kapal (
shipService) dan form SINKARKES (v2/cop.controller.js&cop_controller.js) saat memilih Ferry - [x] Penambahan unit test komprehensif pada lapisan validasi, servis, dan controller (100% coverage pada kode baru)
- [x] Skema enum
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-09-29 16:00 | Hermansyah Efendi | DEPLOYED | Branch: development |
| 2 | Staging (UAT) | 2026-10-03 15:55 | achmad fauzi | DEPLOYED | Release Candidate v1.18.5 via MR !2083. Co-deployed: [ND-274-LNSW.2-2026, ND-374-LNSW.2-2026, TASK-FIX-COMMENTED-BIND-PARAMS-V1.18.4, TASK-UNIFY-DB-POOL-AND-HEALTH-OBSERVABILITY, TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026] |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-10-03 | Staging | Promosi ke STAGING via MR !2083 (Release Candidate v1.18.5) | Hermansyah Efendi |
| 2026-09-29 | Development | Inisialisasi draft audit log dari context block backend (commit eff8b09d) | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Superset Kapal (Docking / Berkegiatan Tetap):
- Jika pengguna memilih
permanent_activity_ship = trueataudocking_ship = true, sistem secara otomatis meresetfl_billing_wartadanfl_billing_sinkarkesdi tabelpengangkut.agentsmenjadifalseviaagentService.updateBillingFlags. - Pada pengajuan keberangkatan (
102...), jika pengajuan kedatangan terkait (service_number_kedatangan) memiliki status Single Billing aktif (fl_billing_warta === trueataufl_billing_sinkarkes === true), maka penandaanpermanent_activity_shipataudocking_shipdilarang keras dan sistem melemparkanBadRequestError.
- Jika pengguna memilih
- Layanan SINKARKES (Ferry):
- Nilai field
kapal.jenisdibatasi secara ketat menggunakan Zod enum['Ferry', 'Non-Ferry'](case-insensitive). - Guard mutual exclusivity: jika
isSingleBilling === truedan jenis kapal adalah'Ferry', sistem menolak request denganBadRequestError("Layanan Single Billing tidak tersedia untuk kapal jenis Ferry."). - Jika data pengajuan agen di database telah memiliki flag Single Billing aktif, sistem melarang pemilihan jenis kapal
'Ferry'. - Jika jenis kapal
'Ferry'disimpan/disubmit tanpa Single Billing, sistem secara otomatis menonaktifkan status Single Billing (fl_billing_warta = false, fl_billing_sinkarkes = false) pada tabelpengangkut.agents.
- Nilai field
- Superset Kapal (Docking / Berkegiatan Tetap):
Perubahan Database (DDL / DML):
sql-- Tidak ada perubahan skema database (DDL/DML) untuk task ini. -- Penyesuaian murni pada layer service logic, schema validasi Zod, dan mutasi atomik flag boolean fl_billing_warta & fl_billing_sinkarkes pada tabel pengangkut.agents.Kontrak API & Response Payload:
POST/super-set/ship&PUT/super-set/ship/:serviceNumber- Deskripsi: Simpan dan perbarui data Superset Kapal (kedatangan & keberangkatan) dengan penegakan auto-reset flag Single Billing dan validasi mutual exclusivity kapal docking/kegiatan tetap.
- Autentikasi / Guard:
Bearer Token(doIntrospection,checkAccessAgentByServiceNumber/checkAccessShipByServiceNumber) - Request Header / Params:
Header:Authorization: Bearer <token>Params:serviceNumber(pada PUT)
- Request Body (Fragment Identitas Kapal Docking / Kegiatan Tetap):json
{ "service_number": "1012026090002", "identity": { "docking_ship": true, "permanent_activity_ship": false } } - Response Payload (
200 OK/201 Created):json{ "data": { "id_ship": 106, "service_number": "1012026090002" }, "message": "Data kapal berhasil disimpan", "statusCode": 201, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Pengajuan kedatangan menggunakan Single Billing. Kapal tidak dapat memilih Kapal Berkegiatan Tetap atau Kapal Docking pada keberangkatan.", "statusCode": 400, "success": false }
PUT/data-cop&POST/submit-cop- Deskripsi: Simpan draft (
saveFormSINKARKES) dan submit (sendFormSINKARKES) permohonan sertifikat sanitasi kapal (COP/SINKARKES) dengan validasi enum jenis kapal dan mutual exclusivity Single Billing. - Autentikasi / Guard:
Bearer Token(doIntrospection) - Request Header:
Header:Authorization: Bearer <token>
- Request Body (Fragment Form SINKARKES):json
{ "service_number": "1012026090003", "isSingleBilling": false, "kapal": { "jenis": "Ferry" } } - Response Payload (
200 OK):json{ "data": { "service_number": "1012026090003" }, "message": "Form SINKARKES berhasil disimpan", "statusCode": 200, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Layanan Single Billing tidak tersedia untuk kapal jenis Ferry.", "statusCode": 400, "success": false }
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
pengangkut-backend - Branch Utama:
development - Merge Request / PR: MR !2083 (Release Candidate v1.18.5)
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
eff8b09d0e155119a7802387fa9fc8a38c38a156 | development | Hermansyah Efendi | 2026-09-29 16:00 | feat(billing): enforce single billing exclusivity for ferry and docking vessels |
Cuplikan Kode / Implementasi Inti
// app/services/ship.service.js
const isPermanentOrDocking = identity?.permanent_activity_ship === true || identity?.docking_ship === true;
if (isOutward) {
await parseWithZod(ShipDataKeberangkatanSchema, request);
if (dataAgent.service_number_kedatangan) {
const arrivalAgent = await agentService.findOneByServiceNumber(dataAgent.service_number_kedatangan);
const isArrivalBillingActive = !!(arrivalAgent?.fl_billing_warta || arrivalAgent?.fl_billing_sinkarkes);
if (isArrivalBillingActive && isPermanentOrDocking) {
throw new BadRequestError('Pengajuan kedatangan menggunakan Single Billing. Kapal tidak dapat memilih Kapal Berkegiatan Tetap atau Kapal Docking pada keberangkatan.');
}
}
}
// Di dalam eksekusi db.transaction:
if (isPermanentOrDocking) {
promises.push(agentService.updateBillingFlags(serviceNumber, {
fl_billing_warta: false,
fl_billing_sinkarkes: false,
}, transaction));
}// app/services/agent.service.js
async updateBillingFlags(serviceNumber, flags, transaction = null) {
const updateData = {};
if (flags.fl_billing_warta !== undefined) updateData.fl_billing_warta = flags.fl_billing_warta;
if (flags.fl_billing_sinkarkes !== undefined) updateData.fl_billing_sinkarkes = flags.fl_billing_sinkarkes;
return Agent.update(updateData, {
where: { service_number: serviceNumber },
transaction,
});
}// app/validations/cop.validation.js
export const KapalJenisSinkarkesEnum = z.preprocess(
(val) => (typeof val === 'string' ? val.trim() : val),
z.enum(['Ferry', 'Non-Ferry', 'ferry', 'non-ferry']).transform((val) => {
const lower = val.toLowerCase();
return lower === 'ferry' ? 'Ferry' : 'Non-Ferry';
})
);5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/validations/cop.validation.test.jstests/services/agent.service.test.jstests/services/ship.service.test.jstests/controllers/v2/cop.controller.test.jstests/controllers/cop_controller.test.js
- Status Eksekusi CLI:
PASSED(105 tests passed)
Output Raw CLI Test Runner
RUN v4.1.11 D:/WORK/LNSW/pengangkut-backend
✓ tests/validations/cop.validation.test.js (15 tests) 42ms
✓ tests/services/agent.service.test.js (19 tests) 53ms
✓ tests/controllers/v2/cop.controller.test.js (17 tests) 29ms
✓ tests/services/ship.service.test.js (49 tests) 102ms
✓ tests/controllers/cop_controller.test.js (5 tests) 17ms
Test Files 5 passed (5)
Tests 105 passed (105)
Start at 16:05:41
Duration 1.65s (transform 951ms, setup 271ms, import 2.96s, tests 242ms, environment 1ms)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:
Agen Pelayaran / PPJK dengan Hak Akses SSm Pengangkut - Hasil Pengujian UAT:
PENDING - Catatan QA: —
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Auto-disable Single Billing pada Superset Kapal (Happy Path):
- Pre-kondisi: Pengajuan kapal kedatangan dibuat dengan Single Billing aktif (
fl_billing_sinkarkes = true). - Trigger / Request:
POST /super-set/shipdengan payloadidentity.docking_ship = trueatauidentity.permanent_activity_ship = true. - Ekspektasi Response: HTTP
201 Created. - Verifikasi Database / Log: Record di tabel
pengangkut.agentsuntuk nomor aju tersebut ter-update menjadifl_billing_warta = falsedanfl_billing_sinkarkes = false.
- Pre-kondisi: Pengajuan kapal kedatangan dibuat dengan Single Billing aktif (
Skenario 2 — Penolakan Docking pada Keberangkatan Ber-Single Billing (Negative Case):
- Pre-kondisi: Aju kedatangan memiliki Single Billing aktif. Buat aju keberangkatan (
102...) yang berelasi ke kedatangan tersebut viaservice_number_kedatangan. - Trigger / Request:
POST /super-set/shippada aju keberangkatan dengan payloadidentity.docking_ship = trueatauidentity.permanent_activity_ship = true. - Ekspektasi Response: HTTP
400 Bad Request("Pengajuan kedatangan menggunakan Single Billing. Kapal tidak dapat memilih Kapal Berkegiatan Tetap atau Kapal Docking pada keberangkatan."). - Verifikasi Database / Log: Data kapal tidak tersimpan; transaksi dibatalkan (rollback).
- Pre-kondisi: Aju kedatangan memiliki Single Billing aktif. Buat aju keberangkatan (
Skenario 3 — Penolakan Single Billing pada Kapal Ferry di SINKARKES (Negative Case):
- Pre-kondisi: Form SINKARKES dibuka.
- Trigger / Request:
PUT /data-copatauPOST /submit-copdengan payloadkapal.jenis: "Ferry"danisSingleBilling: true. - Ekspektasi Response: HTTP
400 Bad Request("Layanan Single Billing tidak tersedia untuk kapal jenis Ferry."). - Verifikasi Database / Log: Form ditolak sebelum update data agen atau persistensi draft sanitasi.
Skenario 4 — Auto-disable Single Billing saat Memilih Ferry (Happy Path):
- Pre-kondisi: Pengajuan awal memiliki flag billing aktif di
pengangkut.agents. - Trigger / Request:
PUT /data-copatauPOST /submit-copdengankapal.jenis: "Ferry"tanpa Single Billing (isSingleBilling: falseatauundefined). - Ekspektasi Response: HTTP
200 OK. - Verifikasi Database / Log: Record di tabel
pengangkut.agentster-update menjadifl_billing_warta = falsedanfl_billing_sinkarkes = false.
- Pre-kondisi: Pengajuan awal memiliki flag billing aktif di
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}