Sekarang kita sampai ke pertanyaan yang paling sering ditanyakan pengguna: “Berapa saldo saya sekarang?” dan “Berapa total pengeluaran bulan ini?” Pada bagian ini, kita implementasikan finance_summary yang menghitung total pemasukan, total pengeluaran, dan saldo bersih dari seluruh transaksi yang tersimpan.
Konsep #
Ringkasan keuangan membutuhkan tiga angka:
- Total pemasukan — jumlah seluruh transaksi
TXN_INCOME. - Total pengeluaran — jumlah seluruh transaksi
TXN_EXPENSE. - Saldo bersih — pemasukan dikurangi pengeluaran.
Mekanisme kerja: iterasi sekali melalui array transaksi. Untuk setiap transaksi, cek type: bila TXN_INCOME, tambah amount ke total_income; bila TXN_EXPENSE, tambah amount ke total_expense. Setelah loop, balance = total_income - total_expense.
Pola ini memanfaatkan fakta bahwa amount selalu positif (divalidasi oleh transaction_create). Tanda negatif hanya muncul di transaction_signed_value() yang dipakai untuk display. Untuk perhitungan ringkasan, kita pakai amount asli agar mudah dibedakan mana income mana expense.
Implementasi #
Fungsi finance_summary:
void finance_summary(const Finance *f, double *total_income,
double *total_expense, double *balance) {
double inc = 0.0;
double exp = 0.0;
if (f != NULL) {
for (int i = 0; i < f->count; i++) {
if (f->items[i].type == TXN_INCOME) {
inc += f->items[i].amount;
} else {
exp += f->items[i].amount;
}
}
}
if (total_income != NULL) {
*total_income = inc;
}
if (total_expense != NULL) {
*total_expense = exp;
}
if (balance != NULL) {
*balance = inc - exp;
}
}Penggunaan di main.c:
static void do_summary(const Finance *f) {
double inc, exp, bal;
finance_summary(f, &inc, &exp, &bal);
printf("\n=== Ringkasan Keuangan ===\n");
printf("Total pemasukan : Rp%12.2f\n", inc);
printf("Total pengeluaran: Rp%12.2f\n", exp);
printf("Saldo bersih : Rp%12.2f\n", bal);
}Verifikasi perhitungan:
/* demo_summary.c - Uji coba finance_summary */
#include <stdio.h>
#include "finance.h"
int main(void) {
Finance f;
finance_init(&f);
finance_add(&f, TXN_INCOME, 5000000.0, "Gaji", "Gaji", "2024-03-01");
finance_add(&f, TXN_EXPENSE, 50000.0, "Bensin", "Transport", "2024-03-02");
finance_add(&f, TXN_EXPENSE, 30000.0, "Makan siang", "Makan", "2024-03-02");
finance_add(&f, TXN_INCOME, 500000.0, "Bonus", "Gaji", "2024-03-05");
finance_add(&f, TXN_EXPENSE, 100000.0, "Belanja", "Hiburan", "2024-03-10");
double inc, exp, bal;
finance_summary(&f, &inc, &exp, &bal);
printf("Total pemasukan : Rp%12.2f\n", inc);
printf("Total pengeluaran: Rp%12.2f\n", exp);
printf("Saldo bersih : Rp%12.2f\n", bal);
/* Test dengan pointer NULL: hanya hitung yang diminta. */
double only_balance;
finance_summary(&f, NULL, NULL, &only_balance);
printf("\nHanya balance: Rp%12.2f (harus sama dengan di atas)\n",
only_balance);
/* Test dengan Finance NULL. */
finance_summary(NULL, &inc, &exp, &bal);
printf("\nFinance NULL: income=%.2f expense=%.2f balance=%.2f\n",
inc, exp, bal);
/* Test dengan Finance kosong. */
Finance empty;
finance_init(&empty);
finance_summary(&empty, &inc, &exp, &bal);
printf("Finance kosong: income=%.2f expense=%.2f balance=%.2f\n",
inc, exp, bal);
return 0;
}Output yang Dihasilkan:
Total pemasukan : Rp 5500000.00
Total pengeluaran: Rp 180000.00
Saldo bersih : Rp 5320000.00
Hanya balance: Rp 5320000.00 (harus sama dengan di atas)
Finance NULL: income=0.00 expense=0.00 balance=0.00
Finance kosong: income=0.00 expense=0.00 balance=0.00Mari kita telaah baris-baris kode di atas:
- Baris 5-6 (defensive NULL check): Bila
f == NULL, kita lewati loop dan kembalikan 0 untuk semua output. Ini lebih baik daripada segfault. Bila pointer output NULL (mis. pemanggil hanya butuh balance), kita tetap menghitung semua lalu menulis hanya yang diminta. - Baris 9-15 (loop per type): Cek
typesetiap transaksi.TXN_INCOMEmenambahinc,TXN_EXPENSEmenambahexp. Karena enum hanya punya dua nilai valid (1 dan -1),elsesudah cukup untuk menangkap expense tanpa eksplisit cek== TXN_EXPENSE. - Baris 17-26 (output parameter check): Tulis hasil hanya bila pointer tidak NULL. Pola ini memungkinkan pemanggil “minta sebagian” tanpa harus menyediakan variabel dummy untuk semua parameter.
- Baris 13 (
%12.2f): Format specifier dengan lebar 12 dan 2 desimal. Alignment kanan cocok untuk angka uang. Bila angka negatif (saldo minus), tanda minus muncul di paling kiri. - Baris 51-53 (defensive NULL Finance): Bila
fNULL, kita tetap keluarkan 0 ke pointer output (yang tidak NULL). Ini memberi behavior yang konsisten: pemanggil selalu mendapat angka valid.
Kode Lengkap #
Kode lengkap untuk finance_summary ada di finance.c. Deklarasi di finance.h:
void finance_summary(const Finance *f, double *total_income,
double *total_expense, double *balance);Pengujian #
Edge case yang harus dipastikan:
- Finance dengan hanya income →
total_expense= 0,balance=total_income. - Finance dengan hanya expense →
total_income= 0,balance=-total_expense. - Finance kosong → semua 0.
- Finance dengan income dan expense berimbang →
balance= 0. - Pointer
fNULL → semua 0 (tidak crash). - Pointer output NULL → tidak crash, hanya tulis yang tidak NULL.
gcc -std=c11 -Wall -Wextra -o /tmp/demo_summary demo_summary.c \
finance.c transaction.c -lm
/tmp/demo_summary