Model Transaction sudah siap. Sekarang kita bangun Finance sebagai wadah koleksi transaksi dengan kemampuan menambah dan menghapus. Sama seperti Inventory di proyek sebelumnya, Finance memakai array statis dengan next_id untuk menjamin id unik. Yang berbeda: tiap elemen punya tipe (income/expense) yang akan kita pakai di perhitungan saldo di bagian 4.

Konsep #

Operasi dasar koleksi Finance:

  1. finance_add — tambah transaksi baru. Fungsi menerima field transaksi (bukan Transaction* jadi), memanggil transaction_create pada slot kosong, dan mengembalikan id baru.
  2. finance_delete — hapus transaksi berdasarkan id. Pakai memmove untuk menggeser elemen setelahnya, sama seperti pola di Inventory.
  3. finance_print_all — cetak seluruh transaksi dengan format tabel.

Mekanisme kerja: array items dialokasikan di stack dengan ukuran FINANCE_CAPACITY = 512. Id increment dari next_id yang dimulai dari 1. Bila kapasitas penuh, finance_add mengembalikan -1 dan tidak mengubah state.

Implementasi #

Header finance.h:

c
/* finance.h - Koleksi transaksi dengan operasi dasar */
#ifndef FINANCE_H
#define FINANCE_H

#include "transaction.h"

#define FINANCE_CAPACITY 512

typedef struct {
    Transaction items[FINANCE_CAPACITY];
    int  count;
    int  next_id;
} Finance;

void finance_init(Finance *f);
int  finance_add(Finance *f, TxnType type, double amount,
                const char *description, const char *category,
                const char *date);
int  finance_delete(Finance *f, int id);
void finance_print_all(const Finance *f);

#endif /* FINANCE_H */

Implementasi:

c
/* finance.c - Implementasi operasi dasar Finance */
#include "finance.h"
#include <stdio.h>
#include <string.h>

void finance_init(Finance *f) {
    if (f == NULL) {
        return;
    }
    f->count = 0;
    f->next_id = 1;
}

int finance_add(Finance *f, TxnType type, double amount,
                const char *description, const char *category,
                const char *date) {
    if (f == NULL || description == NULL ||
        category == NULL || date == NULL) {
        return -1;
    }
    if (f->count >= FINANCE_CAPACITY) {
        return -1;
    }
    Transaction *slot = &f->items[f->count];
    int id = f->next_id;
    if (transaction_create(slot, id, type, amount,
                          description, category, date) != 0) {
        return -1;
    }
    f->next_id++;
    f->count++;
    return id;
}

int finance_delete(Finance *f, int id) {
    if (f == NULL) {
        return -1;
    }
    for (int i = 0; i < f->count; i++) {
        if (f->items[i].id == id) {
            memmove(&f->items[i], &f->items[i + 1],
                    (size_t)(f->count - i - 1) * sizeof(Transaction));
            f->count--;
            return 0;
        }
    }
    return -1;
}

void finance_print_all(const Finance *f) {
    if (f == NULL) {
        return;
    }
    printf("Total transaksi: %d/%d\n", f->count, FINANCE_CAPACITY);
    for (int i = 0; i < f->count; i++) {
        transaction_print(&f->items[i]);
    }
}

Verifikasi operasi dasar:

c
/* demo_finance.c - Uji coba add/delete */
#include <stdio.h>
#include "finance.h"

int main(void) {
    Finance f;
    finance_init(&f);

    int id1 = finance_add(&f, TXN_INCOME, 5000000.0,
                          "Gaji", "Gaji", "2024-03-01");
    int id2 = finance_add(&f, TXN_EXPENSE, 50000.0,
                          "Bensin", "Transport", "2024-03-02");
    int id3 = finance_add(&f, TXN_EXPENSE, 30000.0,
                          "Makan", "Makan", "2024-03-02");
    printf("Tambah: id1=%d id2=%d id3=%d count=%d\n",
           id1, id2, id3, f.count);
    finance_print_all(&f);

    /* Hapus id2 dan verifikasi shift. */
    int rc = finance_delete(&f, id2);
    printf("\nSetelah delete id=%d: rc=%d count=%d\n", id2, rc, f.count);
    finance_print_all(&f);

    /* Delete id yang tidak ada. */
    rc = finance_delete(&f, 999);
    printf("\nDelete id=999: rc=%d (harus -1)\n", rc);
    return 0;
}

Output yang Dihasilkan:

text
Tambah: id1=1 id2=2 id3=3 count=3
Total transaksi: 3/512
[0001] 2024-03-01 MASUK  Gaji  Rp  5000000.00  Gaji
[0002] 2024-03-02 KELUAR Transport  Rp   -50000.00  Bensin
[0003] 2024-03-02 KELUAR Makan     Rp   -30000.00  Makan

Setelah delete id=2: rc=0 count=2
Total transaksi: 2/512
[0001] 2024-03-01 MASUK  Gaji  Rp  5000000.00  Gaji
[0003] 2024-03-02 KELUAR Makan     Rp   -30000.00  Makan

Delete id=999: rc=-1 (harus -1)

Mari kita telaah baris-baris kode di atas:

  1. Baris 13-15 (finance_init): Sama seperti inventory_init. Reset count = 0 dan next_id = 1. Tidak perlu zero-initialize array items karena count = 0 sudah menandakan array kosong.
  2. Baris 17-34 (finance_add): Pattern yang sama dengan inventory_add. Id didapat dari next_id sebelum diincrement. Bila transaction_create gagal, fungsi kembali -1 tanpa mengubah count atau next_id. Id yang gagal tidak di-skip, sehingga id urut tetap konsisten (no gap karena validation failure).
  3. Baris 36-47 (finance_delete): Linear search untuk menemukan id, lalu memmove untuk menggeser elemen setelahnya satu posisi kiri. memmove dipakai alih-alih memcpy karena source dan destination bisa overlap. Ukuran yang dipindahkan adalah (f->count - i - 1) * sizeof(Transaction) yang menjadi 0 bila i adalah indeks terakhir (no-op).
  4. Baris 49-55 (finance_print_all): Cetak header dengan kapasitas total (count/capacity) lalu iterasi semua transaksi. Format %d/%d memberi tahu pengguna berapa banyak slot yang sudah dipakai.
  5. Baris 70-71 (verifikasi shift): Setelah delete id=2, transaksi id=3 harus tetap ada di posisi index 1 (bukan 2). memmove menggeser dengan benar.

Kode Lengkap #

Kode lengkap untuk finance.h dan finance.c ada di direktori proyek. Filter dan summary akan ditambahkan di bagian 3 dan 4.

Pengujian #

Test edge case yang harus dipastikan:

  • finance_add dengan parameter NULL mengembalikan -1.
  • finance_add dengan amount negatif mengembalikan -1 (dari transaction_create).
  • finance_add dengan tanggal invalid mengembalikan -1.
  • finance_delete pada id yang sudah dihapus mengembalikan -1.
  • finance_delete pada Finance kosong mengembalikan -1.
  • Setelah finance_delete, count berkurang satu dan elemen setelahnya digeser dengan benar.
bash
gcc -std=c11 -Wall -Wextra -o /tmp/demo_finance demo_finance.c \
    finance.c transaction.c -lm
/tmp/demo_finance