[TASK-SPLIT-BILLING-202608] Implementasi Unified Split Billing dan Bypass Tagihan K/L Modul Pengangkut Kapal
Modul: Pengangkut | Tipe: Feature | Stage Saat Ini:
DEVELOPMENT| Tanggal Dokumen: 2026-08-26
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
TASK-SPLIT-BILLING-202608Sumber Instruksi:
ND-256/LNSW.34/2026 & WA-20260824-split-billingDokumen Referensi / Lampiran: add_status_tagihan_split_billing.sql
Task / Dependensi Terkait (Cross-Service): TASK-SET-EXPIRED-BILLING-V3 — Integrasi API Outbound Set Expired Billing V3 saat void tagihan
Modul Terkait:
PengangkutService / Repository:
pengangkut-backendPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Implementasi arsitektur Unified Split Billing dan mekanisme Bypass Tagihan K/L untuk pengangkut kapal. Memungkinkan admin memproses tagihan K/L yang dibayar secara mandiri/terpisah (bypass) sebelum kode billing Simponi terbit (Pre-Billing Bypass) maupun melakukan pembatalan dan penerbitan ulang billing setelah billing terbentuk (Post-Billing Void-Reset) secara atomik dalam satu transaksi database, terintegrasi dengan pemanggilan API Layanan Single Billing
POST /api/v3/set-expired-billing(idempotent), aman, serta selaras dengan validasi kelayakan (eligibility) dan kalkulasi inquiry Single Billing.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Milestone 1: Database Migration & Schema Definition (Penambahan ENUM
enum_status_tagihan, kolomstatus_tagihan,alasan_split,updated_by,updated_datepadapengangkut.tagihan_hdr, backfill data lama, serta index performa). - [x] Milestone 2: Middleware & Security Guard (Implementasi middleware
requireAdminuntuk memverifikasi role admin via JWT tokengroupGa00,07,20). - [x] Milestone 3: Core Service Logic Split Billing (Implementasi
SplitBillingServicedengan dukungan Pre-Billing Bypass dan Post-Billing Void-Reset secara atomik dalam 1 database transaction). - [x] Milestone 4: Single Billing V3 Integration & Query Optimization (Integrasi pemanggilan API Layanan Single Billing
POST /api/v3/set-expired-billingsaat void tagihan dengan penanganan idempotent already-expired, serta optimasi kuericheckDataBillingmenggunakanWHERE no_aju_layanan = $1). - [x] Milestone 5: Integration with Eligibility & Inquiry (Pembaruan
BillingEligibilityServicedanInquiryBillingServiceuntuk mengenali statusBYPASSED, pemisahan array responsedraft_tagihanhanya untukACTIVEdanbypassed_tagihanuntukBYPASSEDbeserta metadata split). - [x] Milestone 6: Simponi Payload Generator Update (Penyelarasan
payloadSingleBillingV2danpayloadSingleBillingV3agar hanya memproses tagihan berstatusACTIVE). - [x] Milestone 7: API Controller & Strict Zod Validation (Penyediaan endpoint
POST /v2/billing/split-billingdan validasi skema ZodSplitBillingSchemadengan tipe array positive integerid_tagihan_hdr& prefix validasi102). - [x] Milestone 8: Unit Testing & Verification (Pembuatan 1-to-1 Vitest unit test suites dengan 100% code coverage pada modul split billing, client API, controller, validation, dan inquiry billing).
- [x] Milestone 1: Database Migration & Schema Definition (Penambahan ENUM
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-08-26 16:30 | Hermansyah Efendi | DEPLOYED | Branch: feat/split-billing |
| 2 | Staging (UAT) | — | — | PENDING | — |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-08-27 | Development | Pemisahan array draft_tagihan (ACTIVE) dan bypassed_tagihan (BYPASSED) pada response endpoint inquiry billing (InquiryBillingService) | Hermansyah Efendi |
| 2026-08-27 | Development | Integrasi Single Billing API v3 set-expired-billing saat void tagihan, penanganan idempotent expired, optimasi kueri checkDataBilling, dan update test suites (commit 6e94a402, 16b2bcda) | Hermansyah Efendi |
| 2026-08-26 | Development | Inisialisasi draft audit log dari context block backend (commit d97fcdcc) | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Unified Split Billing Entry-Point (
SplitBillingService.splitBilling): Memvalidasi nomor pengajuan layanan wajib ber-prefix102(keberangkatan), me-resolve pasangan nomor aju kedatangan (101...) 1x di entry-point, dan merutekan otomatis kebypassTagihan(Pre-Billing) jika belum ada billing aktif, atau kevoidAndResetTagihan(Post-Billing) jika kode billing aktif sudah terbit. - Integrasi Single Billing V3 Set-Expired (
voidAndResetTagihan): Mengambiltrx_id_billingdari header tagihan aktif dan memanggilbillingApi.setExpiredBillingV3({ trx_id_insw: trxIdBilling, alasan })untuk menandai status expired pada Layanan Single Billing. Diterapkan penanganan idempotent (jika respon menyatakan status sudah expired, proses void lokal tetap dilanjutkan) serta rollback transaksi database atomik jika terjadi failure API lainnya. - Optimalisasi Kueri Single Billing (
checkDataBilling): Mengganti operatorILIKE '%' || $1 || '%'menjadi perbandingan eksakWHERE no_aju_layanan = $1untuk mencegah false match/collision dan mengoptimalkan performa Index Scan. - Eligibility & Inquiry Array Separation: Tagihan berstatus
BYPASSEDdihitung memenuhi syarat kelayakan (completed), diecualikan dari total nominal billing Simponi. Response endpoint inquiry billing memisahkan struktur data menjadi dua array:draft_tagihan(hanya menampung tagihanACTIVEyang masuk ke billing Simponi) danbypassed_tagihan(menampung tagihanBYPASSEDlengkap dengan metadataalasan_split,bypassed_by, danbypassed_at). - Admin Guard & Strict Validation: Endpoint split billing diproteksi oleh middleware
requireAdmin(verifikasi tokengroupGa00,07,20) dan divalidasi oleh ZodSplitBillingSchema(id_tagihan_hdrarray positive integer). - Simponi Payload Filtering: Filter
payloadSingleBillingV2danpayloadSingleBillingV3disesuaikan agar hanya menyertakan tagihan berstatusACTIVE. - Daftar File yang Berubah:
migration/add_status_tagihan_split_billing.sql[DDL/DML]app/constant/billing.constant.js[Constant]app/schemas/tagihan-hdr.schema.js[Schema / Model]app/schemas/tagihan-dtl.schema.js[Schema / Model]app/models/billing_models.js[Model / SQL Helper]app/api/billing.api.js[API Client / Outbound HTTP]app/middlewares/require-admin.js[Middleware]app/validations/billing.validation.js[Validation]app/services/split-billing.service.js[Service / Business Logic]app/services/billing-eligibility.service.js[Service / Business Logic]app/services/inquiry-billing.service.js[Service / Business Logic]app/services/request-billing.service.js[Service / Business Logic]app/controllers/v2/billing.controller.js[Controller / API Route]app/routers/billing_route.js[Controller / API Route]tests/controllers/v2/billing.controller.test.js[Unit Test]tests/api/billing.api.test.js[Unit Test]tests/middlewares/require-admin.test.js[Unit Test]tests/services/split-billing.service.test.js[Unit Test]tests/services/billing-eligibility.service.test.js[Unit Test]tests/services/inquiry-billing.service.test.js[Unit Test]tests/services/request-billing.service.test.js[Unit Test]tests/validations/billing.validation.test.js[Unit Test]CHANGELOG.md[Documentation]
- Unified Split Billing Entry-Point (
Perubahan Database (DDL / DML):
sql-- Step 1: Buat ENUM Type untuk status_tagihan (Strict Type) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.typname = 'enum_status_tagihan' AND n.nspname = 'pengangkut' ) THEN CREATE TYPE pengangkut.enum_status_tagihan AS ENUM ('ACTIVE', 'BYPASSED', 'VOIDED'); END IF; END$$; -- Step 2: Tambah kolom baru ke tagihan_hdr menggunakan ENUM type ALTER TABLE pengangkut.tagihan_hdr ADD COLUMN IF NOT EXISTS status_tagihan pengangkut.enum_status_tagihan DEFAULT 'ACTIVE', ADD COLUMN IF NOT EXISTS updated_by varchar(50) NULL, ADD COLUMN IF NOT EXISTS updated_date timestamptz(0) NULL, ADD COLUMN IF NOT EXISTS alasan_split text NULL; -- Step 3: Backfill data lama agar tidak NULL UPDATE pengangkut.tagihan_hdr SET status_tagihan = 'ACTIVE' WHERE status_tagihan IS NULL; -- Step 4: Index performa untuk query lookup split billing CREATE INDEX IF NOT EXISTS idx_tagihan_hdr_trx_status ON pengangkut.tagihan_hdr (trx_id_insw, status_tagihan);Kontrak API & Response Payload:
1. Endpoint Split Billing (Admin Action)
POST/v2/billing/split-billing- Deskripsi: Endpoint terpadu untuk memisahkan/melewati (bypass) tagihan K/L tertentu dari alur billing kolektif. Backend secara otomatis mendeteksi apakah transaksi berada pada fase Pre-Billing atau Post-Billing.
- Autentikasi / Security Guard:
Bearer TokenJWT (Wajib lolosdoIntrospectiondanrequireAdmindengangroupGa00,07, atau20). - Request Header / Content-Type:
Authorization:Bearer <token>Content-Type:application/json
A. Skema Validasi Request Payload (Zod Schema)
Field Tipe Data Wajib/Opsional Aturan Validasi / Keterangan service_numberStringWajib Nomor pengajuan layanan keberangkatan (Wajib diawali prefix 102).kode_klStringKondisional Enum: "022"(Inaportnet) atau"024"(Sinkarkes).id_tagihan_hdrArray<Number>Kondisional Array positive integers daftar ID header tagihan spesifik yang ingin di-bypass (misal: [105]).alasan_splitStringWajib Keterangan/alasan pengajuan split (Minimal 5 karakter). Aturan Refinement: Request wajib menyertakan minimal salah satu dari
kode_klATAUid_tagihan_hdr.Contoh Payload Request:
json{ "service_number": "10202408260001", "kode_kl": "024", "id_tagihan_hdr": [105], "alasan_split": "K/L Sinkarkes melakukan pembayaran mandiri di instansi" }B. Kondisi 1: Pre-Billing (Saat Kode Billing BELUM Terbit)
- Trigger: Belum ada kode billing aktif pada
billing.subbiller_hdruntuk nomor aju ini. - Aksi Internal: Mengubah status record
tagihan_hdreksisting menjadiBYPASSED(atau membuat header placeholderBYPASSEDjika tagihan K/L belum terkirim sama sekali). - HTTP Status:
200 OK - Response Payload:json
{ "data": { "stage": "PRE_BILLING", "message": "Split billing tagihan K/L berhasil dilakukan (Pre-Billing)", "service_number": "10202408260001", "kode_kl": "024", "status_tagihan": "BYPASSED", "alasan_split": "K/L Sinkarkes melakukan pembayaran mandiri di instansi", "updated_by": "admin-user", "affected_headers": [ { "id_tagihan_hdr": 105, "kode_kl": "024", "status_tagihan": "BYPASSED" } ] }, "message": "Split billing tagihan K/L berhasil dilakukan (Pre-Billing)", "statusCode": 200, "success": true }
C. Kondisi 2: Post-Billing (Saat Kode Billing SUDAH Terbit / Void & Reset)
- Trigger: Kode billing Simponi sudah terbit (
subbiller_hdr.kode_billing_indukada & belum expired), namun belum dibayar (fl_paid = false). - Aksi Internal (Atomic DB Transaction & External Outbound):
- Memanggil Single Billing API
POST /api/v3/set-expired-billingmenggunakantrx_id_billingtagihan aktif. - Menangani respon idempotent jika billing sudah berstatus expired.
- Void seluruh tagihan aktif dalam batch transaksi (
status_tagihan = 'VOIDED'). - Duplikasi data header & detail tagihan baru.
- Tagihan target yang di-split diset
status_tagihan = 'BYPASSED'. - Tagihan lainnya diset
status_tagihan = 'ACTIVE'denganfl_billing = falseagar siap di-generate ulang kode billing Simponi barunya.
- Memanggil Single Billing API
- HTTP Status:
200 OK - Response Payload:json
{ "data": { "stage": "POST_BILLING", "message": "Tagihan aktif berhasil di-void dan di-reset. Silakan lakukan generate billing ulang.", "voided_count": 2, "reset_tagihan": [ { "id_tagihan_hdr_lama": 101, "status_tagihan": "ACTIVE", "hdr": { "id_tagihan_hdr": 201, "status_tagihan": "ACTIVE" }, "dtl_count": 1 }, { "id_tagihan_hdr_lama": 102, "status_tagihan": "BYPASSED", "hdr": { "id_tagihan_hdr": 202, "status_tagihan": "BYPASSED" }, "dtl_count": 1 } ] }, "message": "Tagihan aktif berhasil di-void dan di-reset. Silakan lakukan generate billing ulang.", "statusCode": 200, "success": true }
D. Penanganan Error & Business Guards
- Tagihan Sudah Dibayar (
fl_payment = true):json{ "data": null, "message": "Void tidak dapat dilakukan. Terdapat tagihan yang sudah dibayarkan dalam batch ini.", "statusCode": 400, "success": false, "errors": [] } - Validasi Nomor Aju (Prefix Non-102):json
{ "data": null, "message": "Split billing hanya dapat dilakukan menggunakan nomor aju keberangkatan", "statusCode": 400, "success": false, "errors": [] } - Tagihan Target Tidak Ditemukan:json
{ "data": null, "message": "id_tagihan_hdr 999 tidak ditemukan dalam daftar tagihan aktif transaksi ini.", "statusCode": 400, "success": false, "errors": [] } - Akses Ditolak (Non-Admin Role):json
{ "status": 403, "error": "Forbidden", "error_description": "Endpoint ini hanya dapat diakses oleh Admin" }
2. Integrasi API Eksternal (Outbound to Single Billing)
POST/api/v3/set-expired-billing- Deskripsi: Membatalkan / menandai status expired atas billing induk yang sudah terbit di Layanan Single Billing saat eksekusi void tagihan pada fase Post-Billing.
- Request Header:
Content-Type:application/json
- Request Body:json
{ "trx_id_insw": "TRX-BILLING-20260827-001", "alasan": "K/L Sinkarkes melakukan pembayaran mandiri di instansi" } - Penanganan Khusus (Idempotency): Status
400 Bad Requestdengan pesan error"Billing sudah dalam status expired"atau"sudah expired"diabaikan (handled idempotently), sehingga alur void dan reset tagihan database lokal tetap dilanjutkan.
3. Endpoint Inquiry Billing & Eligibility (Updated)
GET/api/inquiry-billing- Deskripsi: Mengambil ringkasan status billing, evaluasi kelayakan (eligibility check), rincian tagihan aktif, serta status split billing dengan pemisahan array tagihan aktif dan bypassed.
- Autentikasi / Security Guard:
Bearer TokenJWT (doIntrospection,checkAccessAgentByServiceNumber). - Query Parameters:
serviceNumber(String, Wajib): Nomor pengajuan layanan (Kedatangan101...atau Keberangkatan102...).
- HTTP Status:
200 OK - Response Payload:json
{ "data": { "billing_status": { "is_billing": true, "is_generated": false, "is_expired": false, "id_billing": null, "is_paid": false, "total_tagihan": 50000, "is_split_billing": true, "payment_detail": null, "billing_detail": null }, "eligibility": { "is_eligible": true, "message": "Semua syarat terpenuhi (Split Billing: Tagihan Sinkarkes)", "requirements": { "lk3": { "required": true, "completed": true, "name": "LK3", "description": "Laporan Kedatangan dan Keberangkatan Kapal" }, "inaport": { "required": true, "completed": true, "status": "ACTIVE", "name": "Tagihan Warta", "description": "Kementerian Perhubungan" }, "sinkarkes": { "required": true, "completed": true, "status": "BYPASSED", "name": "Tagihan Sinkarkes", "description": "Kementerian Kesehatan" } } }, "draft_tagihan": [ { "id_tagihan_hdr": 101, "trx_id_insw": "10202408260001", "jenis_pengajuan": "KEBERANGKATAN", "kode_kl": "022", "uraian_kl": "KEMENTERIAN PERHUBUNGAN", "uraian_satker": "Kantor Distrik Navigasi Palembang", "jenis_satker": "NONBLU", "nama_jasa": "Jasa Rambu", "nominal_tarif_pnbp": 50000, "volume": 1, "satuan_tarif": "Per Kapal", "total_tarif_per_record": "50000", "tgl_tagihan": "2026-08-26", "status_tagihan": "ACTIVE" } ], "bypassed_tagihan": [ { "id_tagihan_hdr": 102, "trx_id_insw": "10202408260001", "jenis_pengajuan": "KEBERANGKATAN", "kode_kl": "024", "uraian_kl": "KEMENTERIAN KESEHATAN", "uraian_satker": "BKK Palembang", "jenis_satker": "NONBLU", "nama_jasa": "Penerbitan COP", "nominal_tarif_pnbp": 30000, "volume": 1, "satuan_tarif": "Dokumen", "total_tarif_per_record": "30000", "tgl_tagihan": "2026-08-26", "status_tagihan": "BYPASSED", "alasan_split": "K/L Sinkarkes melakukan pembayaran mandiri di instansi", "bypassed_by": "admin-1", "bypassed_at": "2026-08-27 15:30:00" } ] }, "message": "Data billing ditemukan", "statusCode": 200, "success": true }
Rincian Perubahan Field Response (Field Delta Matrix)
Field Path Tipe Data Status Perubahan Deskripsi & Dampak Fungsional data.billing_status.is_split_billingBooleanBaru (Added) Bernilai truejika terdapat minimal satu tagihan K/L berstatusBYPASSED. Digunakan frontend untuk menampilkan badge / indikator split billing.data.billing_status.is_expiredBooleanBaru (Added) Menandakan apakah kode billing Simponi yang pernah terbit sudah melewati tanggal kedaluwarsa atau fl_expired = true.data.billing_status.total_tagihanNumberDiubah (Modified Logic) Kalkulasi nominal tagihan kini hanya menjumlahkan tagihan ACTIVE. TagihanBYPASSEDdanVOIDEDotomatis dikecualikan dari total.data.eligibility.requirements.inaport.statusString | nullBaru (Added) Status eksplisit tagihan Warta Kemenhub ( "ACTIVE","BYPASSED", ataunull).data.eligibility.requirements.sinkarkes.statusString | nullBaru (Added) Status eksplisit tagihan Sinkarkes Kemenkes ( "ACTIVE","BYPASSED", ataunull).data.eligibility.messageStringDiubah (Modified Format) Format pesan kelayakan kini menyertakan info split jika ada tagihan di-bypass (misal: "Semua syarat terpenuhi (Split Billing: Tagihan Sinkarkes)").data.draft_tagihan[]Array<Object>Diubah (Modified Structure) Hanya berisi array tagihan berstatus ACTIVEyang berkontribusi terhadaptotal_tagihandan diproses ke Simponi Single Billing.data.bypassed_tagihan[]Array<Object>Baru (Added Array) Berisi array tagihan yang di-bypass ( status_tagihan: "BYPASSED") lengkap dengan metadata split (alasan_split,bypassed_by,bypassed_at).
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
pengangkut-backend - Branch Utama:
feat/split-billing - Merge Request / PR:
-
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
16b2bcda | feat/split-billing | Hermansyah Efendi | 2026-08-27 15:23 | docs(changelog): update unreleased entries |
6e94a402 | feat/split-billing | Hermansyah Efendi | 2026-08-27 15:21 | feat(billing): integrate single billing set-expired-billing v3 API and optimize query |
1319246c | feat/split-billing | Hermansyah Efendi | 2026-08-27 11:56 | fix(billing): enforce positive integer validation for id_tagihan_hdr in split billing schema |
90e79bb1 | feat/split-billing | Hermansyah Efendi | 2026-08-26 17:34 | refactor(billing): rename rebilling terminology to voidAndResetTagihan and reset_tagihan |
d97fcdcc | feat/split-billing | Hermansyah Efendi | 2026-08-26 16:47 | feat(billing): implement unified split billing and bypass tagihan KL module |
Cuplikan Kode / Implementasi Inti
// 1. Entry point terpadu split billing pada app/services/split-billing.service.js
async splitBilling({ service_number, kode_kl, id_tagihan_hdr = [], alasan_split, admin_id }) {
const prefix = (service_number || '').slice(0, 3);
if (prefix !== '102') {
throw new BadRequestError('Split billing hanya dapat dilakukan menggunakan nomor aju keberangkatan');
}
const serviceNumberKedatangan = await checkAjuKedatangan(service_number);
const trx_ids = serviceNumberKedatangan
? [service_number, serviceNumberKedatangan]
: [service_number];
const existingBilling = await checkDataBilling(service_number);
const isBillingGenerated = existingBilling.length > 0 && existingBilling[0].is_active;
if (!isBillingGenerated) {
const result = await this.bypassTagihan({
trx_ids,
service_number,
kode_kl,
id_tagihan_hdr,
alasan_split,
admin_id,
});
return { stage: 'PRE_BILLING', ...result };
} else {
const result = await this.voidAndResetTagihan({
trx_ids,
service_number,
kode_kl,
bypassed_tagihan: id_tagihan_hdr,
alasan_split,
admin_id,
existingBilling,
});
return { stage: 'POST_BILLING', ...result };
}
}
// 2. Integrasi Set Expired Billing V3 saat void tagihan pada voidAndResetTagihan
// Guard: Ambil trx_id_billing dari active tagihan headers
const trxIdBilling = activeTagihanList.find(h => !!h.trx_id_billing)?.trx_id_billing || billingData[0]?.trx_id_insw;
if (!trxIdBilling) {
logger.warn({ service_number }, 'Void rejected: No trx_id_billing found in active tagihan headers');
throw new BadRequestError('Tidak ditemukan trx_id_billing pada tagihan aktif untuk transaksi ini.');
}
// Step 1: Set status billing menjadi expired di Layanan Single Billing
logger.debug({ service_number, trx_id_billing: trxIdBilling }, 'Calling billingApi.setExpiredBillingV3');
try {
await billingApi.setExpiredBillingV3({
trx_id_insw: trxIdBilling,
alasan: alasan_split || null,
});
} catch (error) {
const isAlreadyExpired = error?.message?.toLowerCase().includes('sudah dalam status expired')
|| error?.message?.toLowerCase().includes('sudah expired');
if (isAlreadyExpired) {
logger.info(
{ service_number, trx_id_billing: trxIdBilling, error_message: error.message },
'Billing is already marked as expired in Single Billing. Proceeding with void and reset'
);
} else {
throw error;
}
}
// Step 2: Void semua tagihan aktif dalam batch di database lokal
const [voidedCount] = await TagihanHdr.update(
{
status_tagihan: STATUS_TAGIHAN.VOIDED,
alasan_split: alasan_split,
updated_by: admin_id,
updated_date: new Date(),
},
{
where: { id_tagihan_hdr: { [Op.in]: Array.from(activeIds) } },
transaction: t,
}
);
// 3. Pemisahan struktur data draft_tagihan & bypassed_tagihan pada app/services/inquiry-billing.service.js
let draftTagihan = [];
let bypassedTagihan = [];
if (serviceNumberKedatangan && serviceNumberKeberangkatan) {
const rawDraftTagihan = await dataDraftTagihan(serviceNumberKedatangan, serviceNumberKeberangkatan);
(rawDraftTagihan || []).forEach(({ id_tagihan_dtl, status_tagihan, bypassed_by, bypassed_at, alasan_split, ...item }) => {
if (status_tagihan === STATUS_TAGIHAN.BYPASSED) {
bypassedTagihan.push({
...item,
status_tagihan: STATUS_TAGIHAN.BYPASSED,
alasan_split: alasan_split || null,
bypassed_by: bypassed_by || null,
bypassed_at: bypassed_at || null,
});
} else {
draftTagihan.push({
...item,
status_tagihan: STATUS_TAGIHAN.ACTIVE,
});
}
});
}
return {
billing_status: { ... },
eligibility: { ... },
draft_tagihan: draftTagihan,
bypassed_tagihan: bypassedTagihan,
};5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/validations/billing.validation.test.js,tests/api/billing.api.test.js,tests/controllers/v2/billing.controller.test.js,tests/services/split-billing.service.test.js,tests/services/inquiry-billing.service.test.js - Status Eksekusi CLI:
PASSED
RUN v4.1.10 D:/WORK/LNSW/pengangkut-backend
✓ tests/validations/billing.validation.test.js (15 tests) 20ms
✓ tests/api/billing.api.test.js (24 tests) 49ms
✓ tests/controllers/v2/billing.controller.test.js (5 tests) 33ms
✓ tests/services/split-billing.service.test.js (20 tests) 72ms
✓ tests/services/inquiry-billing.service.test.js (6 tests) 16ms
Test Files 5 passed (5)
Tests 70 passed (70)
Duration 1.05sB. Verifikasi Integrasi Staging / Production
Diisi saat naik ke Staging atau Production.
{
"test_suites": [
"tests/validations/billing.validation.test.js (PASS)",
"tests/api/billing.api.test.js (PASS)",
"tests/controllers/v2/billing.controller.test.js (PASS)",
"tests/services/split-billing.service.test.js (PASS)",
"tests/services/inquiry-billing.service.test.js (PASS)"
],
"verification_status": "DEV_PASSED"
}- Hasil Pengujian UAT:
PENDING - Catatan QA: Unit test lokal (70 tests across 5 test suites) telah lulus secara empiris. Menunggu jadwal deployment dan verifikasi staging.