Skip to content

[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/2026

  • Sumber Instruksi: Nota Dinas Resmi ND-274/LNSW.2/2026

  • Dokumen Referensi / Lampiran:

  • Task / Dependensi Terkait (Cross-Service): Tidak ada

  • Modul Terkait: Pengangkut

  • Service / Repository: pengangkut-backend

  • PIC 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 exclusivity isSingleBilling pada SINKARKES
    • [x] Method updateBillingFlags pada agentService untuk mutasi atomik flag billing di pengangkut.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)

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-09-29 16:00Hermansyah EfendiDEPLOYEDBranch: development
2Staging (UAT)2026-10-03 15:55achmad fauziDEPLOYEDRelease 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]
3Production——PENDING—

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-10-03StagingPromosi ke STAGING via MR !2083 (Release Candidate v1.18.5)Hermansyah Efendi
2026-09-29DevelopmentInisialisasi draft audit log dari context block backend (commit eff8b09d)Hermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    1. Superset Kapal (Docking / Berkegiatan Tetap):
      • Jika pengguna memilih permanent_activity_ship = true atau docking_ship = true, sistem secara otomatis mereset fl_billing_warta dan fl_billing_sinkarkes di tabel pengangkut.agents menjadi false via agentService.updateBillingFlags.
      • Pada pengajuan keberangkatan (102...), jika pengajuan kedatangan terkait (service_number_kedatangan) memiliki status Single Billing aktif (fl_billing_warta === true atau fl_billing_sinkarkes === true), maka penandaan permanent_activity_ship atau docking_ship dilarang keras dan sistem melemparkan BadRequestError.
    2. Layanan SINKARKES (Ferry):
      • Nilai field kapal.jenis dibatasi secara ketat menggunakan Zod enum ['Ferry', 'Non-Ferry'] (case-insensitive).
      • Guard mutual exclusivity: jika isSingleBilling === true dan jenis kapal adalah 'Ferry', sistem menolak request dengan BadRequestError ("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 tabel pengangkut.agents.
  • 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) ​

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
eff8b09d0e155119a7802387fa9fc8a38c38a156developmentHermansyah Efendi2026-09-29 16:00feat(billing): enforce single billing exclusivity for ferry and docking vessels

Cuplikan Kode / Implementasi Inti ​

javascript
// 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));
}
javascript
// 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,
    });
}
javascript
// 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.js
    • tests/services/agent.service.test.js
    • tests/services/ship.service.test.js
    • tests/controllers/v2/cop.controller.test.js
    • tests/controllers/cop_controller.test.js
  • Status Eksekusi CLI: PASSED (105 tests passed)
Output Raw CLI Test Runner ​
text
 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) ​
  1. 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/ship dengan payload identity.docking_ship = true atau identity.permanent_activity_ship = true.
    • Ekspektasi Response: HTTP 201 Created.
    • Verifikasi Database / Log: Record di tabel pengangkut.agents untuk nomor aju tersebut ter-update menjadi fl_billing_warta = false dan fl_billing_sinkarkes = false.
  2. 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 via service_number_kedatangan.
    • Trigger / Request: POST /super-set/ship pada aju keberangkatan dengan payload identity.docking_ship = true atau identity.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).
  3. Skenario 3 — Penolakan Single Billing pada Kapal Ferry di SINKARKES (Negative Case):

    • Pre-kondisi: Form SINKARKES dibuka.
    • Trigger / Request: PUT /data-cop atau POST /submit-cop dengan payload kapal.jenis: "Ferry" dan isSingleBilling: 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.
  4. 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-cop atau POST /submit-cop dengan kapal.jenis: "Ferry" tanpa Single Billing (isSingleBilling: false atau undefined).
    • Ekspektasi Response: HTTP 200 OK.
    • Verifikasi Database / Log: Record di tabel pengangkut.agents ter-update menjadi fl_billing_warta = false dan fl_billing_sinkarkes = false.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend