Anggaran di Bagian 2 menjawab pertanyaan “apakah saya melampaui batas?”. Pada Bagian 3 kita menjawab pertanyaan “ke mana uang saya pergi?”. Laporan ringkasan menampilkan total pemasukan, pengeluaran, dan saldo. Analisis persentase memecah pengeluaran per kategori sehingga pengguna tahu kategori terbesar dan dapat menyesuaikan prioritas.
Agregasi Bertahap #
Analisis keuangan membangun agregasi dalam beberapa tahap. Tahap pertama: hitung total pemasukan dan pengeluaran per bulan. Tahap kedua: pecah pengeluaran per kategori. Tahap ketiga: hitung persentase setiap kategori terhadap total. Setiap tahap menggunakan koleksi Kotlin secara fungsional, tanpa loop mutable.
Implementasi Laporan dan Analisis #
Kode berikut menunjukkan laporan ringkasan dan analisis persentase:
// Bagian 3: Laporan ringkasan dan analisis pengeluaran.
// Mendemonstrasikan agregasi grup, persentase, dan identifikasi kategori
// dengan pengeluaran terbesar.
enum class JenisTransaksi { PEMASUKAN, PENGELUARAN }
data class Transaksi(
val id: String,
val jenis: JenisTransaksi,
val jumlah: Long,
val kategori: String,
val bulan: String
)
class BukuKeuangan {
private val transaksi = mutableListOf<Transaksi>()
fun tambah(t: Transaksi) { transaksi.add(t) }
fun ringkasanBulanan(bulan: String): Triple<Long, Long, Long> {
val filtered = transaksi.filter { it.bulan == bulan }
val pemasukan = filtered.filter { it.jenis == JenisTransaksi.PEMASUKAN }.sumOf { it.jumlah }
val pengeluaran = filtered.filter { it.jenis == JenisTransaksi.PENGELUARAN }.sumOf { it.jumlah }
return Triple(pemasukan, pengeluaran, pemasukan - pengeluaran)
}
fun pengeluaranPerKategori(bulan: String): List<Pair<String, Long>> {
return transaksi
.filter { it.bulan == bulan && it.jenis == JenisTransaksi.PENGELUARAN }
.groupBy { it.kategori }
.mapValues { (_, list) -> list.sumOf { it.jumlah } }
.toList()
.sortedByDescending { it.second }
}
fun persentasePengeluaran(bulan: String): List<Triple<String, Long, Double>> {
val perKat = pengeluaranPerKategori(bulan)
val total = perKat.sumOf { it.second }
if (total == 0L) return emptyList()
return perKat.map { (k, v) -> Triple(k, v, v.toDouble() / total * 100) }
}
fun kategoriTerbesar(bulan: String): Pair<String, Long>? = pengeluaranPerKategori(bulan).firstOrNull()
}
fun formatRupiah(nilai: Long): String {
val str = nilai.toString()
return "Rp" + str.reversed().chunked(3).joinToString(".").reversed()
}
fun main() {
val buku = BukuKeuangan()
val bulan = "2026-01"
buku.tambah(Transaksi("T001", JenisTransaksi.PEMASUKAN, 5_000_000, "gaji", bulan))
buku.tambah(Transaksi("T002", JenisTransaksi.PENGELUARAN, 750_000, "makan", bulan))
buku.tambah(Transaksi("T003", JenisTransaksi.PENGELUARAN, 1_200_000, "transport", bulan))
buku.tambah(Transaksi("T004", JenisTransaksi.PENGELUARAN, 500_000, "makan", bulan))
buku.tambah(Transaksi("T005", JenisTransaksi.PENGELUARAN, 600_000, "hiburan", bulan))
buku.tambah(Transaksi("T006", JenisTransaksi.PENGELUARAN, 350_000, "makan", bulan))
val (masuk, keluar, saldo) = buku.ringkasanBulanan(bulan)
println("=== Ringkasan Bulan $bulan ===")
println("Pemasukan : ${formatRupiah(masuk)}")
println("Pengeluaran: ${formatRupiah(keluar)}")
println("Saldo : ${formatRupiah(saldo)}")
println()
println("=== Pengeluaran per Kategori (urut desc) ===")
buku.pengeluaranPerKategori(bulan).forEach { (k, v) ->
println("%-12s %s".format(k, formatRupiah(v)))
}
println()
println("=== Persentase Pengeluaran ===")
buku.persentasePengeluaran(bulan).forEach { (k, v, pct) ->
println("%-12s %s (%.1f%%)".format(k, formatRupiah(v), pct))
}
println()
val terbesar = buku.kategoriTerbesar(bulan)
println("Kategori terbesar: ${terbesar?.first} (${formatRupiah(terbesar?.second ?: 0)})")
}Output yang Dihasilkan:
=== Ringkasan Bulan 2026-01 ===
Pemasukan : Rp5.000.000
Pengeluaran: Rp3.400.000
Saldo : Rp1.600.000
=== Pengeluaran per Kategori (urut desc) ===
makan Rp1.600.000
transport Rp1.200.000
hiburan Rp600.000
=== Persentase Pengeluaran ===
makan Rp1.600.000 (47.1%)
transport Rp1.200.000 (35.3%)
hiburan Rp600.000 (17.6%)
Kategori terbesar: makan (Rp1.600.000)Mari kita bedah logika analisis:
Triple<Long, Long, Long>: tipe kembalian ringkasan bulanan. Tiga nilai (pemasukan, pengeluaran, saldo) dibungkus dalam satu objek. Untuk lebih dari tiga nilai,data classlebih cocok.val (masuk, keluar, saldo) = buku.ringkasanBulanan(bulan): destructuring declaration. Triple dapat dibongkar ke tiga variabel sekaligus.groupBy { it.kategori }.mapValues { ... }.toList().sortedByDescending { it.second }: pipeline empat tahap.groupBymengelompokkan,mapValuesmenjumlahkan,toListmengubah ke List,sortedByDescendingmengurutkan dari besar ke kecil.v.toDouble() / total * 100: konversiLongkeDoubleuntuk pembagian pecahan. Hasilnya persen dengan satu desimal.if (total == 0L) return emptyList(): penanganan kasus tidak ada pengeluaran. Mencegah pembagian dengan nol.pengeluaranPerKategori(bulan).firstOrNull(): ambil elemen pertama ataunulljika list kosong. MengembalikanPair<String, Long>?agar pemanggil dapat menangani kasus null.
Persentase vs Nominal #
Persentase kadang lebih informatif daripada nominal. Rp1.600.000 untuk kategori makan terdengar besar, tetapi jika total pengeluaran Rp3.400.000, persentasenya 47%, yang berarti hampir setengah pengeluaran untuk makan. Pengguna dapat membandingkan apakah proporsi tersebut sehat atau perlu ditekan.
Kategori Terbesar dan Pengambilan Keputusan #
Mengetahui kategori terbesar membantu pengambilan keputusan. Jika kategori terbesar adalah makan, pengguna mungkin perlu menyiapkan makan sendiri. Jika transport, mungkin perlu naik transportasi umum. Tanpa data ini, keputusan ditentukan oleh perasaan, bukan fakta.
Referensi Lanjutan #
- Kotlin Collections overview — pengantar koleksi Kotlin untuk agregasi laporan bulanan dan persentase pengeluaran
- Kotlin Sequences — dokumentasi Sequences untuk pemrosesan transaksi besar secara lazy saat menyusun laporan
Ringkasan Bagian 3 #
Laporan ringkasan dan analisis kini berfungsi: ringkasanBulanan() memberi total pemasukan/pengeluaran/saldo, pengeluaranPerKategori() mengurutkan kategori dari besar ke kecil, dan persentasePengeluaran() menunjukkan proporsi setiap kategori. Pipeline fungsional groupBy-map-sort menjadi pola yang berulang. Pada Bagian 4 kita menambahkan penyimpanan ke file JSON dengan validasi input yang ketat.