[ND-374/LNSW.2/2026] Penyesuaian Skema Penarikan Data Jenis Kapal pada Superset Kapal SSm Pengangkut dan Validasi Referensi Kapal Asing & Non-Trayek
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 validasi referensi jenis kapal telah diterapkan di lingkungan Staging (UAT) untuk proses verifikasi QA.
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
ND-374/LNSW.2/2026Sumber Instruksi: Nota Dinas Resmi ND-374/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: Pengisian data "Jenis Kapal" pada SSm Pengangkut sebelumnya berupa isian manual mengacu pada tabel referensi. Pada kapal Trayek berbendera Indonesia, data "Jenis Kapal" sebenarnya telah tersedia pada data trayek Inaportnet yang ditarik saat inisiasi permohonan. Perbedaan pengisian manual oleh agen pelayaran dengan data tarikan trayek kerap memicu kendala dalam permohonan warta kapal serta penerbitan PKK maupun SPB di Inaportnet. Oleh karena itu, dilakukan penyesuaian sistem: untuk kapal Trayek berbendera Indonesia, data Jenis Kapal otomatis diambil dari tarikan data trayek dan dikunci (tidak dapat diubah manual); sedangkan untuk kapal Non-Trayek (baik bendera Indonesia maupun asing) serta kapal Trayek berbendera asing, pengisian Jenis Kapal divalidasi ketat terhadap tabel referensi
tr_tipe_kapalkarena tidak ada penarikan data otomatis dari Inaportnet.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Standardisasi elemen data: mengganti validasi legacy
typemenjaditype_shippadaShipNonTrayekIdentitySchemadiapp/validations/ship-identity.validation.js - [x] Implementasi private method validasi referensi jenis kapal (
#validateShipType(isNonTrayek, identity)) padaapp/services/ship.service.jsmenggunakan cache referensitr_tipe_kapal(REF_JENIS_KAPAL_ALL) - [x] Penerapan aturan kondisional validasi: kapal Non-Trayek (seluruh bendera) dan kapal Trayek asing (
flag !== 'ID') wajib valid terhadap referensi; kapal Trayek bendera Indonesia (flag === 'ID') dilewati karena bersumber dari tarikan trayek Inaportnet - [x] Fallback kompatibilitas kolom legacy: pemetaan
typedankindotomatis mengacu padatype_shipdi#prepareShipDataForSaving - [x] Sinkronisasi pemetaan data
type_shippada tarikan Inaportnet (kapalJenisDetail/jenisDetail), penataan payload raw, dan penguncian override trayek - [x] Penambahan dan pembaruan unit test komprehensif pada
tests/services/ship.service.test.jsdantests/validations/ship-identity.validation.test.js - [x] Penyusunan skenario uji mendalam dan Postman Test Suite Collection (
ND-374_penyesuaian_skema_penarikan_jenis_kapal_postman_collection.json)
- [x] Standardisasi elemen data: mengganti validasi legacy
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-09-29 11:33 | 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-374-LNSW.2-2026, ND-274-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-30 | Development | Pelengkapan skenario QA Playbook (mapping route type 0–4, cURL commands) dan penambahan Postman Collection ND-374 | Hermansyah Efendi |
| 2026-09-29 | Development | Penyesuaian aturan validasi: memperluas validasi referensi untuk seluruh kapal Non-Trayek (ID & Asing) serta fallback type/kind legacy | Hermansyah Efendi |
| 2026-09-29 | Development | Inisialisasi draft audit log dari context block backend (commit a180e191 s.d. 806eeb93) | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
Klasifikasi Route Type (Tipe Trayek): Sistem mengkategorikan rute kapal berdasarkan nilai integer
route_type/id_route_type:Kode Nama Trayek Keterangan Validasi type_ship0NON-TRAYEK Wajib Validasi Referensi (Bendera ID maupun Asing, karena tidak ada data trayek Inaportnet) 1LINER Bendera ID: Otomatis & Terkunci; Bendera Asing: Wajib Validasi Referensi 2TRAMPER Bendera ID: Otomatis & Terkunci; Bendera Asing: Wajib Validasi Referensi 3PKKA Bendera ID: Otomatis & Terkunci; Bendera Asing: Wajib Validasi Referensi 4PPKN Bendera ID: Otomatis & Terkunci; Bendera Asing: Wajib Validasi Referensi Validasi Jenis Kapal Kondisional: Private method
#validateShipType(isNonTrayek, identity)diShipServicemengevaluasi kondisi:javascriptconst shouldValidate = isNonTrayek || (flag && flag !== 'ID');- Kapal Non-Trayek (
isNonTrayek = true): Baik berbendera asing maupun Indonesia (flag === 'ID'), wajib divalidasi ke tabel referensitr_tipe_kapal(kode_tipe_kapal) melalui cache RedisREF_JENIS_KAPAL_ALL(TTL 12 jam) karena penarikan data ke Inaportnet tidak dilakukan (getDataShipInaportmengembalikan data kosong). - Kapal Trayek (
isNonTrayek = false): Jika berbendera Indonesia (flag === 'ID'), validasi referensi dilewati karena datatype_shipotomatis ditarik dari Inaportnet (kapalJenisDetail/jenisDetail) dan dikunci viaoldShip.type_ship. Jika berbendera asing, validasi referensi tetap diberlakukan.
- Kapal Non-Trayek (
Backward Compatibility Database: Pada method
#prepareShipDataForSaving, kolom legacytypedankinddiberi fallback keidentity.type_ship:javascripttype: identity.type || identity.type_ship, kind: identity.kind || identity.type_ship,Hal ini menjaga persistensi tabel database yang masih mereferensikan kolom warisan tersebut.
Penyelarasan Schema Zod: Field
typepadaShipNonTrayekIdentitySchemadigantikan olehtype_shipdengan sanitasinoForbiddenSymbols("Jenis Kapal").min(1).max(100).
Perubahan Database (DDL / DML):
sql-- Tidak ada perubahan skema database (DDL/DML) untuk task ini. -- Penyesuaian murni pada layer service logic, schema validasi, serta konsumsi tabel referensi pengangkut.tr_tipe_kapal yang telah tersedia.Kontrak API & Response Payload:
1.
GET/ref/jenis-kapal-all- Deskripsi: Mengambil seluruh daftar master data referensi jenis kapal (
tr_tipe_kapal) yang dicache di RedisREF_JENIS_KAPAL_ALL(TTL 12 jam). - Autentikasi / Guard:
Bearer Token - Request Header:
Authorization: Bearer <token>Accept: application/json
- Response Payload (
200 OK):json{ "code": "200", "message": "success", "datas": [ { "id_tipe_kapal": "1", "kode_tipe_kapal": "1.3.8", "tipe_kapal": "TONGKANG MINYAK (OIL BARGE)", "id_map": "8", "enum_tipe": "tongkang" }, { "id_tipe_kapal": "2", "kode_tipe_kapal": "1.4.5", "tipe_kapal": "TONGKANG PENGANGKUT GAS (LPG)", "id_map": "8", "enum_tipe": "tongkang" }, { "id_tipe_kapal": "15", "kode_tipe_kapal": "1.7.1", "tipe_kapal": "KAPAL BARANG UMUM (GENERAL CARGO)", "id_map": "1", "enum_tipe": "barang" } ] }
2.
PUT/super-set/ship/:serviceNumber&POST/super-set/ship- Deskripsi: Simpan (POST) dan perbarui (PUT) data Superset Kapal (kedatangan & keberangkatan).
- Autentikasi / Guard:
Bearer Token(doIntrospection,checkAccessAgentByServiceNumber/checkAccessShipByServiceNumber) - Request Header / Params:
Header:Authorization: Bearer <token>,Content-Type: application/jsonParams:serviceNumber(pada PUT)
- Request Body Payload (Struktur Lengkap):json(Catatan: Pada
{ "service_number": "101019B86C56C", "registration_number": "AL.203/2000/04786/25937/25", "trayek": "pkka", "identity": { "code": "030108", "name": "EVER BEFIT", "flag": "TW", "siupal": "BX-79/AL203", "kind": "KAPAL MUATAN PETI KEMAS / CONTAINER", "domicile": null, "call_sign": "aaaa", "inmarsat_number": "aaaa", "cso_name": null, "cso_phone": null, "voyage_number": "aaaaVVVV", "captain_name": "HOANG VU DAI", "shipping_type": "INTERNATIONAL", "route": "Shekou/China, Semarang/Tanjung Emas, Tanjung Priok, Kaohsiung", "permanent_activity_ship": null, "imo": "9784128", "type_ship": "1.3.8", "registration_port": "CINA", "mmsi": "asd" }, "specification": { "ship_width": "32.80", "back_draft": "1", "max_draft": "1", "front_draft": "1", "air_height": null, "minimum_safe_manning": "1", "length_overall": "211.00", "driving_force": "24260.00", "TahunPembuatan": 2017, "deadweight_ton": "36972.00", "gross_ton": "32691.00" }, "ports": { "transit_port": null, "unloading_port": null, "next_port": null, "before_origin_port": "TWKHH", "raw_route": [ { "id": 1, "port_flag": "CN", "port_code": "CNSHK", "port_name": "Shekou/China" }, { "id": 2, "port_flag": "ID", "port_code": "IDSRG", "port_name": "Semarang/Tanjung Emas" }, { "id": 3, "port_flag": "ID", "port_code": "IDJKT", "port_name": "Tanjung Priok" }, { "id": 4, "port_flag": "TW", "port_code": "TWKHH", "port_name": "Kaohsiung" } ], "origin_nation": "CN", "origin_port": "CNSHK", "destination_port": "TWKHH", "landing_site": "IDJKT.A01", "windows_status": null, "pandu": [] }, "dates": { "estimate_arrival": "2026-10-01 00:00", "actual_arrival": null, "unload": null, "load": null, "estimate_depature": "2026-10-02 00:00", "actual_depature": null } }POST /super-set/ship, fieldid_shipdihilangkan). - Response Payload (
200 OK/201 Created):json{ "data": { "service_number": "101019B86C56C" }, "message": "Data kapal kedatangan berhasil disimpan", "statusCode": 200, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Jenis kapal tidak valid atau tidak terdaftar di referensi!", "statusCode": 400, "success": false }
- Deskripsi: Mengambil seluruh daftar master data referensi jenis kapal (
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 |
|---|---|---|---|---|
806eeb93277978d514445864f02a7a8adf878b69 | development | Hermansyah Efendi | 2026-09-29 10:50 | Merge branch 'development' of https://scm.insw.go.id/lnsw/pengangkut/pengangkut-backend into development |
a5647c055642dbd972ecdeff9a93c8867576ceb6 | development | Hermansyah Efendi | 2026-09-29 10:41 | mend |
1bf6595fd978004c73bf42c77dc1252aca6b30b3 | development | Hermansyah Efendi | 2026-09-29 10:41 | mend |
a180e191610352eb26dc08640416f592c4e2d354 | development | Hermansyah Efendi | 2026-09-29 10:40 | feat(ship): validate ship type against reference for foreign vessels |
Cuplikan Kode / Implementasi Inti
// app/services/ship.service.js
class ShipService {
async #validateShipType(isNonTrayek, identity) {
const flag = identity?.flag?.trim()?.toUpperCase();
const shouldValidate = isNonTrayek || (flag && flag !== 'ID');
if (shouldValidate) {
const cacheKey = CacheKeys.REF_JENIS_KAPAL_ALL();
const refJenisKapal = await getOrSetCache(cacheKey, REF_CACHE_TTL, () => getJenisKapalAll());
const validCodes = new Set(
Array.isArray(refJenisKapal)
? refJenisKapal.map((item) => item.kode_tipe_kapal?.trim()).filter(Boolean)
: []
);
const typeShip = identity?.type_ship?.trim();
if (!typeShip || !validCodes.has(typeShip)) {
return false;
}
return true;
}
return true;
}
#prepareShipDataForSaving(data) {
// ...
const result = {
// ...
type: identity.type || identity.type_ship,
kind: identity.kind || identity.type_ship,
type_ship: identity.type_ship,
};
// ...
}
// Pemanggilan di dalam upsertSupersetShip sebelum db.transaction:
const validateShipType = await this.#validateShipType(isNonTrayek, identity);
if (!validateShipType) throw new BadRequestError(`Jenis kapal tidak valid atau tidak terdaftar di referensi!`);// app/validations/ship-identity.validation.js
export const ShipNonTrayekIdentitySchema = BaseIdentitySchema.extend({
imo: noForbiddenSymbols("IMO").min(1).max(100),
code: noForbiddenSymbols("Code").min(1).max(100),
name: noForbiddenSymbols("Name").min(1).max(100),
flag: noForbiddenSymbols("Flag").min(1).max(100),
siupal: noForbiddenSymbols("Siupal").min(1).max(100),
type_ship: noForbiddenSymbols("Jenis Kapal").min(1).max(100),
}).superRefine(validateTongkangShipCaptain);5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/services/ship.service.test.jstests/validations/ship-identity.validation.test.js
- Status Eksekusi CLI:
PASSED
Cuplikan Kode Unit Test
// tests/services/ship.service.test.js
it('skips type_ship validation when ship is trayek with flag ID', async () => {
agentService.findOneByServiceNumber.mockResolvedValueOnce({
id_agent: 16, registration_ship_number: 'REG7', id_route_type: 1, // Trayek (LINER)
});
Ship.findOne.mockResolvedValueOnce(null);
db.query.mockResolvedValueOnce([{ estimate_locked: false, actual_locked: false }]);
Ship.create.mockResolvedValueOnce({ id_ship: 34 });
const result = await shipService.upsertSupersetShip({
service_number: '101-ID-FLAG',
identity: { flag: 'ID', type_ship: 'ANYTHING_OR_EMPTY' },
specification: {},
dates: {},
ports: {
origin_port: 'IDTPP',
destination_port: 'IDAKR',
raw_route: [{ port_code: 'IDTPP' }, { port_code: 'IDAKR' }],
pandu: [],
},
}, { userId: 1 }, {});
expect(result).toBeDefined();
});
it('throws BadRequestError when non-trayek ship with flag ID has invalid type_ship', async () => {
agentService.findOneByServiceNumber.mockResolvedValueOnce({
id_agent: 17, registration_ship_number: 'REG8', id_route_type: 0, // Non-Trayek
});
Ship.findOne.mockResolvedValueOnce(null);
db.query.mockResolvedValueOnce([{ estimate_locked: false, actual_locked: false }]);
await expect(shipService.upsertSupersetShip({
service_number: '101-NON-TRAYEK-ID',
identity: { flag: 'ID', type_ship: 'INVALID_CODE' },
specification: {},
dates: {},
ports: { pandu: [] },
}, { userId: 1 }, {})).rejects.toThrow('Jenis kapal tidak valid atau tidak terdaftar di referensi!');
});Output Raw CLI Test Runner
RUN v4.1.11 D:/WORK/LNSW/pengangkut-backend
✓ tests/validations/ship-identity.validation.test.js (3 tests) 13ms
✓ tests/services/ship.service.test.js (47 tests) 65ms
Test Files 2 passed (2)
Tests 50 passed (50)
Start at 11:30:54
Duration 652ms (transform 285ms, setup 0ms, import 498ms, tests 78ms, 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) - Kebutuhan Akses / Role Akun:
Akun Agen Pelayaran / PPJK (Bearer Token) - Environment Base URL:
https://api-stg.insw.go.id/api/pengangkut - Postman Collection File:
ND-374_penyesuaian_skema_penarikan_jenis_kapal_postman_collection.json - Dokumen Panduan Excel:
ND-374-LNSW.2-2026_QA_Playbook.xlsx - Hasil Pengujian UAT:
PENDING - Catatan QA: —
Pemetaan Parameter Route Type (Trayek)
Nilai Integer (route_type / id_route_type) | Nama Rute | Sifat Validasi type_ship |
|---|---|---|
0 | NON-TRAYEK | Wajib Lolos Referensi (Bendera ID maupun Asing) |
1 | LINER | Bendera ID: Bypass Inaportnet; Bendera Asing: Wajib Referensi |
2 | TRAMPER | Bendera ID: Bypass Inaportnet; Bendera Asing: Wajib Referensi |
3 | PKKA | Bendera ID: Bypass Inaportnet; Bendera Asing: Wajib Referensi |
4 | PPKN | Bendera ID: Bypass Inaportnet; Bendera Asing: Wajib Referensi |
Pemetaan Variabel Lingkungan & Service Number (Postman Collection)
| Kategori Rute & Bendera | Variabel Postman | Default Service Number Staging | Nomor Registrasi Izin | Entitas Kapal Uji |
|---|---|---|---|---|
| Trayek TRAMPER (Flag ID) | | 101019B86C4B4 | AL.103/2000/72476/90951/25 | VARIA USAHA 9 |
| Trayek PKKA (Flag Non-ID / Asing) | | 101019B86C56C | AL.203/2000/04786/25937/25 | EVER BEFIT |
| NON-TRAYEK (Semua Bendera) | | 101019B66BB12 | (string kosong "") | MV. DOLPHIN 82 |
Alur Prasyarat Pengujian (Frontend) & Penanganan Kendala Gateway Inaportnet
Inisiasi Data Permohonan Agen (Melalui Frontend / UI):
- Sebelum melakukan simulasi penyimpanan atau update data kapal (
PUT /super-set/ship/:serviceNumber), tester/QA wajib melakukan pembuatan data permohonan Agen terlebih dahulu melalui UI Frontend SSm Pengangkut. - Pada formulir pembuatan data Agen, pengguna wajib menginput:
- Jenis Trayek: Pilih jenis trayek sesuai skenario uji, misalnya
TRAMPER(kapal domestik) atauPKKA(kapal asing). - Nomor Registrasi: Masukkan nomor izin operasional yang sah, misalnya:
- Kapal Domestik Trayek ID:
AL.103/2000/72476/90951/25 - Kapal Berbendera Asing:
AL.203/2000/04786/25937/25
- Kapal Domestik Trayek ID:
- Jenis Trayek: Pilih jenis trayek sesuai skenario uji, misalnya
- Setelah data Agen berhasil tersimpan, sistem akan membentuk nomor aju (
service_number). - Tester selanjutnya dapat berpindah ke halaman / tab Kapal untuk mengeksekusi skenario uji (baik melalui form UI maupun menggunakan endpoint API Postman/cURL dengan menyertakan
service_numberyang telah terbentuk).
- Sebelum melakukan simulasi penyimpanan atau update data kapal (
Mitigasi Kendala Gateway Inaportnet (Transient Error 500 / TLS Socket Drop):
- Saat inisiasi permohonan di halaman Agen, backend SSm Pengangkut melakukan komunikasi real-time ke server API Inaportnet untuk validasi izin dan penarikan data warta/trayek.
- Dikarenakan environment development/staging Inaportnet memiliki kestabilan infrastruktur yang fluktuatif, proses pembuatan aju terkadang mengalami kegagalan dengan response HTTP 500 dan log socket TLS berikut:text
"Client network socket disconnected before secure TLS connection was established" "D04D3E8B597F0000:error:0A0003F2:SSL routines:ssl3_read_bytes:ssl/tls alert unexpected message:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:918:SSL alert number 10\n" - Tindakan QA / Mitigasi: Error di atas murni merupakan transient SSL/TLS handshake timeout dari gateway Inaportnet dan bukan bug internal backend SSm Pengangkut. QA disarankan melakukan submit ulang (retry) beberapa saat kemudian hingga koneksi socket Inaportnet berhasil terjalin.
Skenario Pengujian QA Detail
Skenario 0 — Pengambilan Master Referensi Jenis Kapal Valid
- Tujuan: Mendapatkan daftar seluruh kode jenis kapal (
kode_tipe_kapal) yang sah. - Trigger / cURL:bash
curl --url 'https://api-stg.insw.go.id/api/pengangkut/ref/jenis-kapal-all' \ -H 'Authorization: Bearer <TOKEN>' \ -H 'Accept: application/json' - Ekspektasi Response: HTTP
200 OK,datasberisi array objek dengan kolomkode_tipe_kapal(contoh:"1.3.8","1.4.5","1.7.1").
Skenario 1 — Kapal Trayek Berbendera Indonesia (Otomatis & Terkunci)
- Pre-kondisi: Permohonan AJU kapal Trayek TRAMPER (
id_route_type = 2,registration_number = 'AL.103/2000/72476/90951/25',service_number = '101019B86C4B4') berbendera Indonesia (flag = 'ID'). Data warta telah tersinkronisasi dengan Inaportnet. - Trigger / cURL:bash
curl --location --request PUT 'https://api-stg.insw.go.id/api/pengangkut/super-set/ship/101019B86C4B4' \ --header 'Authorization: Bearer <TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "service_number": "101019B86C4B4", "registration_number": "AL.103/2000/72476/90951/25", "trayek": "tramper", "identity": { "code": "2010 Ba No. 2100/L", "name": "VARIA USAHA 9", "flag": "ID", "siupal": "B XXV-1617/AL.58", "kind": "tugboat", "domicile": null, "call_sign": "asd", "inmarsat_number": "N/A", "cso_name": null, "cso_phone": null, "voyage_number": "aaaaVVVV", "captain_name": "Nakhoda", "shipping_type": "DOMESTIK", "route": "Asam-Asam, Biringkassi, Balikpapan, Bunati, Banjarmasin, Cirebon, Ciwandan, Cigading, Cilegon, Gresik, Bojonegara, Kintap, Kendawangan, Kumai, Lamongan, Rembang/Sluke, Pulang Pisau, Sungai Puting, Samarinda, Semarang/Tanjung Emas, Marunda, Ketapang, Boom Baru/Palembang, Tanjung Perak, Tanjung Priok, Tarjun, Labuhan Lombok, Tuban, Pontianak, Dumai, Pasang Kayu, Tanjung Redep, Pangkalpinang, Banka, Panjang, Kotabaru, Tarahan, Makassar, Satui/Sei Danau, Sampit, Teluk Adang dan Tanah Grogot, Asam-Asam, Biringkassi, Balikpapan, Bunati, Banjarmasin, Cirebon, Ciwandan, Cigading, Cilegon, Gresik, Bojonegara, Kintap, Kendawangan, Kumai, Lamongan, Rembang/Sluke, Pulang Pisau, Sungai Puting, Samarinda, Semarang/Tanjung Emas, Marunda, Ketapang, Boom Baru/Palembang, Tanjung Perak, Tanjung Priok, Tarjun, Labuhan Lombok, Tuban, Pontianak, Dumai, Pasang Kayu, Tanjung Redep, Pangkalpinang, Banka, Panjang, Kotabaru, Tarahan, Makassar, Satui/Sei Danau, Sampit, Teluk Adang dan Tanah Grogot", "permanent_activity_ship": null, "imo": "9606998", "type_ship": "1.7.13", "registration_port": "TANJUNG PRIOK", "mmsi": "asd", "tugboat": "N/A", "perintis_ship": null, "docking_ship": null, "tongkang_ship": null, "tugboat_ship": null, "is_coal_carrier": null, "is_tol_laut_ship": null, "is_new_ship": null, "is_sea_trial_ship": null, "is_livestock_carrier_ship": null, "is_short_sea_ship": null, "is_tobe_scrapped_ship": null }, "specification": { "ship_width": "8.00", "back_draft": "1", "max_draft": "1", "front_draft": "1", "air_height": "1", "minimum_safe_manning": "1", "length_overall": "26.00", "driving_force": "1", "TahunPembuatan": "2010", "deadweight_ton": "0", "gross_ton": "189" }, "ports": { "transit_port": null, "unloading_port": null, "next_port": null, "before_origin_port": "IDBPN", "raw_route": [ { "id": 1, "port_flag": "ID", "port_code": "IDIAM", "port_name": "Asam-Asam" }, { "id": 2, "port_flag": "ID", "port_code": "IDBIR", "port_name": "Biringkassi" }, { "id": 3, "port_flag": "ID", "port_code": "IDBPN", "port_name": "Balikpapan" }, { "id": 4, "port_flag": "ID", "port_code": "IDBTI", "port_name": "Bunati" }, { "id": 5, "port_flag": "ID", "port_code": "IDBDJ", "port_name": "Banjarmasin" }, { "id": 6, "port_flag": "ID", "port_code": "IDCBN", "port_name": "Cirebon" }, { "id": 7, "port_flag": "ID", "port_code": "IDCIW", "port_name": "Ciwandan" }, { "id": 8, "port_flag": "ID", "port_code": "IDCIG", "port_name": "Cigading" }, { "id": 9, "port_flag": "ID", "port_code": "IDCGN", "port_name": "Cilegon" }, { "id": 10, "port_flag": "ID", "port_code": "IDGRE", "port_name": "Gresik" }, { "id": 11, "port_flag": "ID", "port_code": "IDBGA", "port_name": "Bojonegara" }, { "id": 12, "port_flag": "ID", "port_code": "IDKNP", "port_name": "Kintap" }, { "id": 13, "port_flag": "ID", "port_code": "IDKDW", "port_name": "Kendawangan" }, { "id": 14, "port_flag": "ID", "port_code": "IDKUM", "port_name": "Kumai" }, { "id": 15, "port_flag": "ID", "port_code": "IDLAM", "port_name": "Lamongan" }, { "id": 16, "port_flag": "ID", "port_code": "IDRNE", "port_name": "Rembang/Sluke" }, { "id": 17, "port_flag": "ID", "port_code": "IDPPS", "port_name": "Pulang Pisau" }, { "id": 18, "port_flag": "ID", "port_code": "IDSPT", "port_name": "Sungai Puting" }, { "id": 19, "port_flag": "ID", "port_code": "IDSRI", "port_name": "Samarinda" }, { "id": 20, "port_flag": "ID", "port_code": "IDSRG", "port_name": "Semarang/Tanjung Emas" }, { "id": 21, "port_flag": "ID", "port_code": "IDMRA", "port_name": "Marunda" }, { "id": 22, "port_flag": "ID", "port_code": "IDKTG", "port_name": "Ketapang" }, { "id": 23, "port_flag": "ID", "port_code": "IDBRU", "port_name": "Boom Baru/Palembang" }, { "id": 24, "port_flag": "ID", "port_code": "IDSUB", "port_name": "Tanjung Perak" }, { "id": 25, "port_flag": "ID", "port_code": "IDJKT", "port_name": "Tanjung Priok" }, { "id": 26, "port_flag": "ID", "port_code": "IDTAR", "port_name": "Tarjun" }, { "id": 27, "port_flag": "ID", "port_code": "IDLLO", "port_name": "Labuhan Lombok" }, { "id": 28, "port_flag": "ID", "port_code": "IDTBN", "port_name": "Tuban" }, { "id": 29, "port_flag": "ID", "port_code": "IDPNK", "port_name": "Pontianak" }, { "id": 30, "port_flag": "ID", "port_code": "IDDUM", "port_name": "Dumai" }, { "id": 31, "port_flag": "ID", "port_code": "IDAYU", "port_name": "Pasang Kayu" }, { "id": 32, "port_flag": "ID", "port_code": "IDTRE", "port_name": "Tanjung Redep" }, { "id": 33, "port_flag": "ID", "port_code": "IDPGK", "port_name": "Pangkalpinang, Banka" }, { "id": 34, "port_flag": "ID", "port_code": "IDPNJ", "port_name": "Panjang" }, { "id": 35, "port_flag": "ID", "port_code": "IDKBU", "port_name": "Kotabaru" }, { "id": 36, "port_flag": "ID", "port_code": "IDTRH", "port_name": "Tarahan" }, { "id": 37, "port_flag": "ID", "port_code": "IDMAK", "port_name": "Makassar" }, { "id": 38, "port_flag": "ID", "port_code": "IDSTU", "port_name": "Satui/Sei Danau" }, { "id": 39, "port_flag": "ID", "port_code": "IDSMQ", "port_name": "Sampit" }, { "id": 40, "port_flag": "ID", "port_code": "IDTNB", "port_name": "Teluk Adang dan Tanah Grogot" }, { "id": 41, "port_flag": "ID", "port_code": "IDIAM", "port_name": "Asam-Asam" }, { "id": 42, "port_flag": "ID", "port_code": "IDBIR", "port_name": "Biringkassi" }, { "id": 43, "port_flag": "ID", "port_code": "IDBPN", "port_name": "Balikpapan" }, { "id": 44, "port_flag": "ID", "port_code": "IDBTI", "port_name": "Bunati" }, { "id": 45, "port_flag": "ID", "port_code": "IDBDJ", "port_name": "Banjarmasin" }, { "id": 46, "port_flag": "ID", "port_code": "IDCBN", "port_name": "Cirebon" }, { "id": 47, "port_flag": "ID", "port_code": "IDCIW", "port_name": "Ciwandan" }, { "id": 48, "port_flag": "ID", "port_code": "IDCIG", "port_name": "Cigading" }, { "id": 49, "port_flag": "ID", "port_code": "IDCGN", "port_name": "Cilegon" }, { "id": 50, "port_flag": "ID", "port_code": "IDGRE", "port_name": "Gresik" }, { "id": 51, "port_flag": "ID", "port_code": "IDBGA", "port_name": "Bojonegara" }, { "id": 52, "port_flag": "ID", "port_code": "IDKNP", "port_name": "Kintap" }, { "id": 53, "port_flag": "ID", "port_code": "IDKDW", "port_name": "Kendawangan" }, { "id": 54, "port_flag": "ID", "port_code": "IDKUM", "port_name": "Kumai" }, { "id": 55, "port_flag": "ID", "port_code": "IDLAM", "port_name": "Lamongan" }, { "id": 56, "port_flag": "ID", "port_code": "IDRNE", "port_name": "Rembang/Sluke" }, { "id": 57, "port_flag": "ID", "port_code": "IDPPS", "port_name": "Pulang Pisau" }, { "id": 58, "port_flag": "ID", "port_code": "IDSPT", "port_name": "Sungai Puting" }, { "id": 59, "port_flag": "ID", "port_code": "IDSRI", "port_name": "Samarinda" }, { "id": 60, "port_flag": "ID", "port_code": "IDSRG", "port_name": "Semarang/Tanjung Emas" }, { "id": 61, "port_flag": "ID", "port_code": "IDMRA", "port_name": "Marunda" }, { "id": 62, "port_flag": "ID", "port_code": "IDKTG", "port_name": "Ketapang" }, { "id": 63, "port_flag": "ID", "port_code": "IDBRU", "port_name": "Boom Baru/Palembang" }, { "id": 64, "port_flag": "ID", "port_code": "IDSUB", "port_name": "Tanjung Perak" }, { "id": 65, "port_flag": "ID", "port_code": "IDJKT", "port_name": "Tanjung Priok" }, { "id": 66, "port_flag": "ID", "port_code": "IDTAR", "port_name": "Tarjun" }, { "id": 67, "port_flag": "ID", "port_code": "IDLLO", "port_name": "Labuhan Lombok" }, { "id": 68, "port_flag": "ID", "port_code": "IDTBN", "port_name": "Tuban" }, { "id": 69, "port_flag": "ID", "port_code": "IDPNK", "port_name": "Pontianak" }, { "id": 70, "port_flag": "ID", "port_code": "IDDUM", "port_name": "Dumai" }, { "id": 71, "port_flag": "ID", "port_code": "IDAYU", "port_name": "Pasang Kayu" }, { "id": 72, "port_flag": "ID", "port_code": "IDTRE", "port_name": "Tanjung Redep" }, { "id": 73, "port_flag": "ID", "port_code": "IDPGK", "port_name": "Pangkalpinang, Banka" }, { "id": 74, "port_flag": "ID", "port_code": "IDPNJ", "port_name": "Panjang" }, { "id": 75, "port_flag": "ID", "port_code": "IDKBU", "port_name": "Kotabaru" }, { "id": 76, "port_flag": "ID", "port_code": "IDTRH", "port_name": "Tarahan" }, { "id": 77, "port_flag": "ID", "port_code": "IDMAK", "port_name": "Makassar" }, { "id": 78, "port_flag": "ID", "port_code": "IDSTU", "port_name": "Satui/Sei Danau" }, { "id": 79, "port_flag": "ID", "port_code": "IDSMQ", "port_name": "Sampit" }, { "id": 80, "port_flag": "ID", "port_code": "IDTNB", "port_name": "Teluk Adang dan Tanah Grogot" } ], "origin_nation": "ID", "origin_port": "IDBPN", "destination_port": "IDBPN", "landing_site": "IDJKT.A01", "windows_status": null, "pandu": [] }, "dates": { "estimate_arrival": "2026-10-02 00:00", "actual_arrival": null, "unload": null, "load": null, "estimate_depature": "2026-10-04 00:00", "actual_depature": null } }' - Ekspektasi Response: HTTP
200 OK/201 Created. Validasi manual referensi dilewati. - Verifikasi Database:sqlNilai
SELECT type_ship, type, kind FROM pengangkut.ships WHERE service_number = '101019B86C4B4';type_shipterkunci dan terisi sesuai data tarikan Inaportnet (kapalJenisDetail/jenisDetail), bukan dari manipulasi input agen.
Skenario 2A — Kapal Non-Trayek dengan Jenis Kapal Valid
- Pre-kondisi: Permohonan AJU kapal Non-Trayek (
id_route_type = 0,trayek = 'non-trayek',registration_number = ''). Seluruh kapal Non-Trayek (baik berbendera domestik maupun asing) wajib divalidasi ke tabel referensipengangkut.tr_tipe_kapal. - Trigger / cURL:bash
curl --location --request PUT 'https://api-stg.insw.go.id/api/pengangkut/super-set/ship/101019B66BB12' \ --header 'Authorization: Bearer <TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "service_number": "101019B66BB12", "registration_number": "", "trayek": "non-trayek", "identity": { "code": "58633-PEXT", "name": "MV. DOLPHIN 82", "flag": "PA", "siupal": "B X-139/AL.001", "kind": "tongkang", "domicile": null, "call_sign": "123", "inmarsat_number": null, "cso_name": null, "cso_phone": null, "voyage_number": "123", "captain_name": "123", "shipping_type": null, "route": null, "permanent_activity_ship": null, "imo": "9300556", "type_ship": "1.4.5", "registration_port": "-", "mmsi": "123", "tugboat": "N/A", "perintis_ship": null, "docking_ship": null, "tongkang_ship": null, "tugboat_ship": true, "is_coal_carrier": null, "is_tol_laut_ship": null, "is_new_ship": null, "is_sea_trial_ship": true, "is_livestock_carrier_ship": null, "is_short_sea_ship": true, "is_tobe_scrapped_ship": null }, "specification": { "ship_width": "32.20", "back_draft": "123", "max_draft": null, "front_draft": "123", "air_height": null, "minimum_safe_manning": "123", "length_overall": "224.93", "driving_force": null, "TahunPembuatan": "2004", "deadweight_ton": "0", "gross_ton": "39035.00" }, "ports": { "transit_port": null, "unloading_port": null, "next_port": null, "before_origin_port": "CLISG", "raw_route": [], "origin_nation": "CN", "origin_port": "IDMUB", "destination_port": "IDMUB", "landing_site": null, "windows_status": null, "pandu": [] }, "dates": { "estimate_arrival": "2026-09-30T07:00:00", "actual_arrival": null, "unload": null, "load": null, "estimate_depature": "2026-10-01T00:00:00", "actual_depature": null }, "id_ship": 5811 }' - Ekspektasi Response: HTTP
200 OK/201 Created. Nilai"1.4.5"lolos verifikasi cacheREF_JENIS_KAPAL_ALL. - Verifikasi Database: Kolom
type_ship,type, dankindterupdate dengan nilai yang sesuai.
Skenario 2B — Kapal Non-Trayek dengan Jenis Kapal Invalid
- Pre-kondisi: Permohonan AJU kapal Non-Trayek (
id_route_type = 0). - Trigger / cURL: Request sama seperti 2A, tetapi
identity.type_ship = "KODE_INVALID_999"atau string kosong"". - Ekspektasi Response: HTTP
400 Bad Request:json{ "data": null, "message": "Jenis kapal tidak valid atau tidak terdaftar di referensi!", "statusCode": 400, "success": false } - Verifikasi Database: Tidak ada perubahan data (transaksi rollback).
Skenario 3A — Kapal Berbendera Asing dengan Jenis Kapal Valid
- Pre-kondisi: Permohonan kapal rute internasional / berbendera asing (contoh:
flag = 'TW', trayekpkka,id_route_type = 3,registration_number = 'AL.203/2000/04786/25937/25'). - Trigger / cURL:bash
curl --location --request PUT 'https://api-stg.insw.go.id/api/pengangkut/super-set/ship/101019B86C56C' \ --header 'Authorization: Bearer <TOKEN>' \ --header 'Content-Type: application/json' \ --data '{ "service_number": "101019B86C56C", "registration_number": "AL.203/2000/04786/25937/25", "trayek": "pkka", "identity": { "code": "030108", "name": "EVER BEFIT", "flag": "TW", "siupal": "BX-79/AL203", "kind": "KAPAL MUATAN PETI KEMAS / CONTAINER", "domicile": null, "call_sign": "aaaa", "inmarsat_number": "aaaa", "cso_name": null, "cso_phone": null, "voyage_number": "aaaaVVVV", "captain_name": "HOANG VU DAI", "shipping_type": "INTERNATIONAL", "route": "Shekou/China, Semarang/Tanjung Emas, Tanjung Priok, Kaohsiung", "permanent_activity_ship": null, "imo": "9784128", "type_ship": "1.3.8", "registration_port": "CINA", "mmsi": "asd" }, "specification": { "ship_width": "32.80", "back_draft": "1", "max_draft": "1", "front_draft": "1", "air_height": null, "minimum_safe_manning": "1", "length_overall": "211.00", "driving_force": "24260.00", "TahunPembuatan": 2017, "deadweight_ton": "36972.00", "gross_ton": "32691.00" }, "ports": { "transit_port": null, "unloading_port": null, "next_port": null, "before_origin_port": "TWKHH", "raw_route": [ { "id": 1, "port_flag": "CN", "port_code": "CNSHK", "port_name": "Shekou/China" }, { "id": 2, "port_flag": "ID", "port_code": "IDSRG", "port_name": "Semarang/Tanjung Emas" }, { "id": 3, "port_flag": "ID", "port_code": "IDJKT", "port_name": "Tanjung Priok" }, { "id": 4, "port_flag": "TW", "port_code": "TWKHH", "port_name": "Kaohsiung" } ], "origin_nation": "CN", "origin_port": "CNSHK", "destination_port": "TWKHH", "landing_site": "IDJKT.A01", "windows_status": null, "pandu": [] }, "dates": { "estimate_arrival": "2026-10-01 00:00", "actual_arrival": null, "unload": null, "load": null, "estimate_depature": "2026-10-02 00:00", "actual_depature": null } }' - Ekspektasi Response: HTTP
200 OK/201 Created. Kode"1.3.8"(Kapal Muatan Peti Kemas) valid ditr_tipe_kapal.
Skenario 3B — Kapal Berbendera Asing dengan Jenis Kapal Invalid
- Pre-kondisi: Permohonan kapal berbendera asing (
flag !== 'ID'). - Trigger / cURL: Request sama seperti 3A, tetapi
identity.type_ship = "INVALID_FOREIGN_CODE". - Ekspektasi Response: HTTP
400 Bad Request:json{ "data": null, "message": "Jenis kapal tidak valid atau tidak terdaftar di referensi!", "statusCode": 400, "success": false }
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}