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:

  1. Total pemasukan — jumlah seluruh transaksi TXN_INCOME.
  2. Total pengeluaran — jumlah seluruh transaksi TXN_EXPENSE.
  3. 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:

c
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:

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:

c
/* 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:

text
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.00

Mari kita telaah baris-baris kode di atas:

  1. 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.
  2. Baris 9-15 (loop per type): Cek type setiap transaksi. TXN_INCOME menambah inc, TXN_EXPENSE menambah exp. Karena enum hanya punya dua nilai valid (1 dan -1), else sudah cukup untuk menangkap expense tanpa eksplisit cek == TXN_EXPENSE.
  3. 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.
  4. 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.
  5. Baris 51-53 (defensive NULL Finance): Bila f NULL, 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:

c
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 f NULL → semua 0 (tidak crash).
  • Pointer output NULL → tidak crash, hanya tulis yang tidak NULL.
bash
gcc -std=c11 -Wall -Wextra -o /tmp/demo_summary demo_summary.c \
    finance.c transaction.c -lm
/tmp/demo_summary