Setelah model Product siap, kita lanjut ke lapisan berikutnya: koleksi produk yang menyimpan banyak Product dan menyediakan operasi CRUD (Create, Read, Update, Delete). Pada bagian ini, kita bangun struct Inventory yang membungkus array statis Product items[INVENTORY_CAPACITY] dengan field count untuk melacak jumlah produk aktif dan next_id untuk menjamin id unik berurutan.

Konsep #

CRUD adalah empat operasi dasar pada koleksi data persisten. Untuk Inventory kita tetapkan kontrak berikut:

  1. Create — inventory_add() menambah produk baru dan mengembalikan id yang baru dialokasikan. Id otomatis diincrement dari next_id.
  2. Read — inventory_find_by_id() dan inventory_find_by_name() mencari produk; inventory_print_all() mencetak seluruh daftar.
  3. Update — inventory_update() memperbarui harga dan stok produk berdasarkan id. Field lain (id, nama) tidak boleh diubah agar integritas referensi tetap terjaga.
  4. Delete — inventory_delete() menghapus produk berdasarkan id dengan memmove untuk menggeser elemen setelahnya.

Mekanisme kerja di balik layar: array items dialokasikan di stack dengan ukuran tetap INVENTORY_CAPACITY = 256. Penambahan baru menulis pada indeks count lalu menambah count. Pencarian linear O(n) cukup untuk kapasitas kecil; bila perlu lebih cepat, kita bisa beralih ke hash table atau binary search (proyek 6 membahas struktur data tersebut).

Implementasi #

Header inventory.h:

c
/* inventory.h - Koleksi produk dengan operasi CRUD */
#ifndef INVENTORY_H
#define INVENTORY_H

#include "product.h"

#define INVENTORY_CAPACITY 256

typedef struct {
    Product items[INVENTORY_CAPACITY];
    int    count;
    int    next_id;
} Inventory;

void inventory_init(Inventory *inv);
int  inventory_add(Inventory *inv, const char *name,
                   double price, int stock);
Product *inventory_find_by_id(Inventory *inv, int id);
int  inventory_find_by_name(Inventory *inv, const char *keyword,
                           Product *out, int out_cap);
int  inventory_update(Inventory *inv, int id,
                     double new_price, int new_stock);
int  inventory_delete(Inventory *inv, int id);
void inventory_print_all(const Inventory *inv);

#endif /* INVENTORY_H */

Implementasi inti CRUD:

c
/* inventory.c - Implementasi operasi CRUD Inventory */
#include "inventory.h"
#include <stdio.h>
#include <string.h>

void inventory_init(Inventory *inv) {
    if (inv == NULL) {
        return;
    }
    inv->count = 0;
    inv->next_id = 1;
}

int inventory_add(Inventory *inv, const char *name,
                  double price, int stock) {
    if (inv == NULL || name == NULL) {
        return -1;
    }
    if (inv->count >= INVENTORY_CAPACITY) {
        return -1;
    }
    Product *slot = &inv->items[inv->count];
    int id = inv->next_id;
    int rc = product_create(slot, id, name, price, stock);
    if (rc != 0) {
        return -1;
    }
    inv->next_id++;
    inv->count++;
    return id;
}

Product *inventory_find_by_id(Inventory *inv, int id) {
    if (inv == NULL) {
        return NULL;
    }
    for (int i = 0; i < inv->count; i++) {
        if (inv->items[i].id == id) {
            return &inv->items[i];
        }
    }
    return NULL;
}

int inventory_update(Inventory *inv, int id,
                     double new_price, int new_stock) {
    Product *p = inventory_find_by_id(inv, id);
    if (p == NULL) {
        return -1;
    }
    if (new_price < 0.0 || new_stock < 0) {
        return -1;
    }
    p->price = new_price;
    p->stock = new_stock;
    return 0;
}

int inventory_delete(Inventory *inv, int id) {
    if (inv == NULL) {
        return -1;
    }
    for (int i = 0; i < inv->count; i++) {
        if (inv->items[i].id == id) {
            memmove(&inv->items[i], &inv->items[i + 1],
                    (size_t)(inv->count - i - 1) * sizeof(Product));
            inv->count--;
            return 0;
        }
    }
    return -1;
}

Mari kita uji operasi CRUD:

c
/* demo_crud.c - Uji coba CRUD Inventory */
#include <stdio.h>
#include "inventory.h"

int main(void) {
    Inventory inv;
    inventory_init(&inv);

    int id1 = inventory_add(&inv, "Beras 5kg", 65000.0, 10);
    int id2 = inventory_add(&inv, "Gula Pasir", 15000.0, 5);
    printf("Tambah: id1=%d id2=%d count=%d\n",
           id1, id2, inv.count);

    Product *p = inventory_find_by_id(&inv, 2);
    printf("Cari id=2: ");
    product_print(p);

    inventory_update(&inv, 1, 70000.0, 8);
    printf("Setelah update id=1: ");
    product_print(&inv.items[0]);

    inventory_delete(&inv, 2);
    printf("Setelah delete id=2, count=%d\n", inv.count);
    inventory_print_all(&inv);
    return 0;
}

Output yang Dihasilkan:

text
Tambah: id1=1 id2=2 count=2
Cari id=2: [000002] Gula Pasir                       Rp  15000.00  stok=   5
Setelah update id=1: [000001] Beras 5kg                        Rp  70000.00  stok=   8
Setelah delete id=2, count=1
Total produk: 1/256
[000001] Beras 5kg                        Rp  70000.00  stok=   8

Mari kita telaah baris-baris kode di atas:

  1. Baris 9-22 (inventory_add): Fungsi menerima data produk (bukan Product* jadi) lalu memanggil product_create() pada slot kosong di akhir array. Id baru didapat dari next_id sebelum diincrement. Pola ini menjamin id selalu unik dan naik monoton.
  2. Baris 24-33 (inventory_find_by_id): Linear search O(n). Mengembalikan pointer ke elemen array (bukan salinan), sehingga pemanggil bisa memodifikasi produk langsung lewat pointer.
  3. Baris 35-44 (inventory_update): Hanya mengizinkan perubahan harga dan stok. Field id dan name sengaja tidak boleh diubah karena bisa memutus referensi dari history transaksi atau laporan.
  4. Baris 46-57 (inventory_delete): Setelah menemukan indeks yang akan dihapus, memmove menggeser seluruh elemen setelahnya satu posisi ke kiri. Berbeda dari memcpy, memmove aman untuk region yang overlap. Setelah itu count dikurangi satu.
  5. Baris 16 (memmove size): Ukuran yang dipindahkan adalah (inv->count - i - 1) * sizeof(Product). Bila i adalah indeks terakhir, ukuran menjadi 0 dan memmove menjadi no-op.

Kode Lengkap #

Kode lengkap untuk bagian ini ada pada berkas inventory.h dan inventory.c. Implementasi inventory_find_by_name() dan inventory_print_all() juga tersedia di berkas yang sama.

Pengujian #

Verifikasi CRUD dengan demo di atas. Untuk test formal, kita akan menambahkan assertion pada bagian 7. Beberapa kasus yang harus dipastikan:

  • inventory_add mengembalikan -1 bila kapasitas penuh.
  • inventory_find_by_id mengembalikan NULL bila id tidak ada.
  • inventory_update menolak harga negatif.
  • inventory_delete pada id yang sudah dihapus mengembalikan -1.
  • inventory_delete menggeser elemen dengan benar (cek dengan inventory_find_by_id).
bash
gcc -std=c11 -Wall -Wextra -o /tmp/demo_crud demo_crud.c \
    inventory.c product.c -lm
/tmp/demo_crud