📦 Modul 4: CRUD Produk

Create, Read, Update, Delete data produk dengan PDO & Bootstrap 5

1. Konsep CRUD

CRUD adalah 4 operasi dasar dalam pengelolaan data:

C
CREATE

Tambah data baru

INSERT INTO
R
READ

Tampilkan data

SELECT FROM
U
UPDATE

Ubah data

UPDATE SET
D
DELETE

Hapus data

DELETE FROM
💡 Alur file CRUD produk:
produk/index.php (READ) → tambah.php (CREATE) → edit.php (UPDATE) → hapus.php (DELETE)
2. READ — Daftar Produk (produk/index.php)
SELECT * FROM produk
📄 produk/index.php READ
<?php
// ============================================
// FILE: produk/index.php
// Menampilkan daftar semua produk (READ)
// ============================================

$pageTitle = 'Daftar Produk';
require_once '../config/database.php';
require_once '../includes/functions.php';
require_once '../includes/header.php';

// ============================================
// QUERY: Ambil semua produk + nama kategori
// JOIN dengan tabel kategori
// ============================================
$sql = "SELECT p.*, k.nama_kategori 
        FROM produk p 
        LEFT JOIN kategori k ON p.id_kategori = k.id
        ORDER BY p.created_at DESC";

$stmt = $pdo->query($sql);
$produkList = $stmt->fetchAll(); // Ambil semua baris sekaligus
// fetchAll() → array of arrays, bisa diloop

// Cek apakah ada pesan sukses dari halaman sebelumnya
$pesan = $_GET['pesan'] ?? '';
?>

<!-- Header Halaman -->
<div class="d-flex justify-content-between align-items-center mb-4">
    <div>
        <h4 class="fw-bold mb-1">📦 Daftar Produk</h4>
        <p class="text-muted mb-0">Total: <strong><?= count($produkList) ?></strong> produk</p>
    </div>
    <a href="tambah.php" class="btn btn-primary">
        <i class="fas fa-plus me-2"></i>Tambah Produk
    </a>
</div>

<!-- Alert Sukses/Error -->
<?php if ($pesan === 'tambah'): ?>
    <div class="alert alert-success alert-dismissible fade show">
        ✅ Produk berhasil ditambahkan!
        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
    </div>
<?php elseif ($pesan === 'edit'): ?>
    <div class="alert alert-info alert-dismissible fade show">
        ✏️ Produk berhasil diperbarui!
        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
    </div>
<?php elseif ($pesan === 'hapus'): ?>
    <div class="alert alert-warning alert-dismissible fade show">
        🗑️ Produk berhasil dihapus!
        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
    </div>
<?php endif; ?>

<!-- Tabel Produk -->
<div class="card border-0 shadow-sm">
    <div class="card-body p-0">
        <div class="table-responsive">
            <table class="table table-hover mb-0">
                <thead class="table-dark">
                    <tr>
                        <th>No</th>
                        <th>Kode</th>
                        <th>Nama Produk</th>
                        <th>Kategori</th>
                        <th>Harga Jual</th>
                        <th class="text-center">Stok</th>
                        <th class="text-center">Aksi</th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (empty($produkList)): ?>
                        <!-- Tampilkan jika tidak ada data -->
                        <tr>
                            <td colspan="7" class="text-center py-5 text-muted">
                                <i class="fas fa-box-open fa-3x mb-3 d-block opacity-25"></i>
                                Belum ada produk. 
                                <a href="tambah.php">Tambah sekarang?</a>
                            </td>
                        </tr>
                    <?php else: ?>
                        <?php foreach ($produkList as $i => $produk): ?>
                        <tr>
                            <td><?= $i + 1 ?></td>
                            <td>
                                <code><?= htmlspecialchars($produk['kode_produk']) ?></code>
                            </td>
                            <td>
                                <strong><?= htmlspecialchars($produk['nama_produk']) ?></strong>
                            </td>
                            <td>
                                <span class="badge bg-secondary">
                                    <?= htmlspecialchars($produk['nama_kategori'] ?? 'Tanpa Kategori') ?>
                                </span>
                            </td>
                            <td><?= formatRupiah($produk['harga_jual']) ?></td>
                            <td class="text-center">
                                <!-- Tampilkan badge merah jika stok menipis -->
                                <span class="badge <?= $produk['stok'] < 10 ? 'bg-danger' : 'bg-success' ?>">
                                    <?= $produk['stok'] ?>
                                </span>
                            </td>
                            <td class="text-center">
                                <!-- Tombol Edit -->
                                <a href="edit.php?id=<?= $produk['id'] ?>" 
                                   class="btn btn-sm btn-warning me-1"
                                   title="Edit">
                                    <i class="fas fa-edit"></i>
                                </a>
                                <!-- Tombol Hapus -->
                                <a href="hapus.php?id=<?= $produk['id'] ?>" 
                                   class="btn btn-sm btn-danger"
                                   title="Hapus"
                                   onclick="return confirm('Yakin hapus produk <?= htmlspecialchars($produk['nama_produk']) ?>?')">
                                    <i class="fas fa-trash"></i>
                                </a>
                            </td>
                        </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<?php require_once '../includes/footer.php'; ?>
3. CREATE — Tambah Produk (produk/tambah.php)
INSERT INTO produk
📄 produk/tambah.php CREATE
<?php
// ============================================
// FILE: produk/tambah.php
// Form tambah produk baru (CREATE)
// ============================================

$pageTitle = 'Tambah Produk';
require_once '../config/database.php';
require_once '../includes/functions.php';
require_once '../includes/header.php';

// Ambil data kategori untuk dropdown
$kategoriList = $pdo->query("SELECT * FROM kategori ORDER BY nama_kategori")->fetchAll();

// Inisialisasi variabel untuk form (isi ulang saat error)
$form = [
    'kode_produk'  => '',
    'nama_produk'  => '',
    'harga_beli'   => '',
    'harga_jual'   => '',
    'stok'         => '',
    'id_kategori'  => '',
];
$errors = [];

// ============================================
// PROSES SIMPAN (saat form di-submit)
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    // 1. Ambil & bersihkan semua input
    $form = [
        'kode_produk'  => trim($_POST['kode_produk'] ?? ''),
        'nama_produk'  => trim($_POST['nama_produk'] ?? ''),
        'harga_beli'   => $_POST['harga_beli'] ?? 0,
        'harga_jual'   => $_POST['harga_jual'] ?? 0,
        'stok'         => $_POST['stok'] ?? 0,
        'id_kategori'  => $_POST['id_kategori'] ?? null,
    ];
    
    // 2. Validasi input
    if (empty($form['kode_produk'])) $errors[] = 'Kode produk harus diisi!';
    if (empty($form['nama_produk'])) $errors[] = 'Nama produk harus diisi!';
    if ($form['harga_jual'] <= 0)   $errors[] = 'Harga jual harus lebih dari 0!';
    if ($form['stok'] < 0)          $errors[] = 'Stok tidak boleh negatif!';
    
    // 3. Cek apakah kode produk sudah ada
    if (empty($errors)) {
        $cek = $pdo->prepare("SELECT id FROM produk WHERE kode_produk = ?");
        $cek->execute([$form['kode_produk']]);
        if ($cek->fetch()) {
            $errors[] = 'Kode produk sudah digunakan, gunakan kode lain!';
        }
    }
    
    // 4. Jika tidak ada error, simpan ke database
    if (empty($errors)) {
        $sql = "INSERT INTO produk 
                    (kode_produk, nama_produk, harga_beli, harga_jual, stok, id_kategori) 
                VALUES 
                    (:kode, :nama, :hbeli, :hjual, :stok, :kategori)";
        
        $stmt = $pdo->prepare($sql);
        $stmt->execute([
            ':kode'     => $form['kode_produk'],
            ':nama'     => $form['nama_produk'],
            ':hbeli'    => $form['harga_beli'],
            ':hjual'    => $form['harga_jual'],
            ':stok'     => $form['stok'],
            ':kategori' => $form['id_kategori'] ?: null,
        ]);
        
        // Redirect dengan pesan sukses
        header('Location: index.php?pesan=tambah');
        exit();
    }
}
?>

<div class="row justify-content-center">
    <div class="col-lg-8">
        
        <!-- Header -->
        <div class="d-flex align-items-center gap-3 mb-4">
            <a href="index.php" class="btn btn-outline-secondary btn-sm">
                <i class="fas fa-arrow-left"></i>
            </a>
            <h4 class="fw-bold mb-0">➕ Tambah Produk Baru</h4>
        </div>
        
        <!-- Tampilkan error jika ada -->
        <?php if (!empty($errors)): ?>
            <div class="alert alert-danger">
                <strong>❌ Terjadi kesalahan:</strong>
                <ul class="mb-0 mt-2">
                    <?php foreach ($errors as $err): ?>
                        <li><?= htmlspecialchars($err) ?></li>
                    <?php endforeach; ?>
                </ul>
            </div>
        <?php endif; ?>
        
        <!-- Form Tambah -->
        <div class="card border-0 shadow-sm">
            <div class="card-body p-4">
                <form method="POST" action="">
                    
                    <div class="row g-3">
                        <!-- Kode Produk -->
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">
                                Kode Produk <span class="text-danger">*</span>
                            </label>
                            <input type="text" name="kode_produk" class="form-control"
                                   value="<?= htmlspecialchars($form['kode_produk']) ?>"
                                   placeholder="cth: PRD011" required>
                            <div class="form-text">Kode unik untuk setiap produk</div>
                        </div>
                        
                        <!-- Nama Produk -->
                        <div class="col-md-8">
                            <label class="form-label fw-semibold">
                                Nama Produk <span class="text-danger">*</span>
                            </label>
                            <input type="text" name="nama_produk" class="form-control"
                                   value="<?= htmlspecialchars($form['nama_produk']) ?>"
                                   placeholder="cth: Indomie Goreng" required>
                        </div>
                        
                        <!-- Kategori -->
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">Kategori</label>
                            <select name="id_kategori" class="form-select">
                                <option value="">-- Pilih Kategori --</option>
                                <?php foreach ($kategoriList as $kat): ?>
                                    <option value="<?= $kat['id'] ?>"
                                        <?= ($form['id_kategori'] == $kat['id']) ? 'selected' : '' ?>>
                                        <?= htmlspecialchars($kat['nama_kategori']) ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        
                        <!-- Stok -->
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">Stok Awal</label>
                            <input type="number" name="stok" class="form-control"
                                   value="<?= $form['stok'] ?>" min="0" placeholder="0">
                        </div>
                        
                        <!-- Harga Beli -->
                        <div class="col-md-6">
                            <label class="form-label fw-semibold">Harga Beli (Modal)</label>
                            <div class="input-group">
                                <span class="input-group-text">Rp</span>
                                <input type="number" name="harga_beli" class="form-control"
                                       value="<?= $form['harga_beli'] ?>" min="0" placeholder="0">
                            </div>
                        </div>
                        
                        <!-- Harga Jual -->
                        <div class="col-md-6">
                            <label class="form-label fw-semibold">
                                Harga Jual <span class="text-danger">*</span>
                            </label>
                            <div class="input-group">
                                <span class="input-group-text">Rp</span>
                                <input type="number" name="harga_jual" class="form-control"
                                       value="<?= $form['harga_jual'] ?>" min="1" placeholder="0" required>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Tombol -->
                    <div class="d-flex gap-2 mt-4">
                        <button type="submit" class="btn btn-primary px-4">
                            <i class="fas fa-save me-2"></i>Simpan Produk
                        </button>
                        <a href="index.php" class="btn btn-outline-secondary">
                            Batal
                        </a>
                    </div>
                    
                </form>
            </div>
        </div>
    </div>
</div>

<?php require_once '../includes/footer.php'; ?>
4. UPDATE — Edit Produk (produk/edit.php)
UPDATE produk SET ... WHERE id = ?
📄 produk/edit.php UPDATE
<?php
// ============================================
// FILE: produk/edit.php
// Form edit produk (UPDATE)
// ============================================

$pageTitle = 'Edit Produk';
require_once '../config/database.php';
require_once '../includes/functions.php';
require_once '../includes/header.php';

// Ambil ID dari URL: edit.php?id=5
$id = (int)($_GET['id'] ?? 0);

// Cari produk berdasarkan ID
$stmt = $pdo->prepare("SELECT * FROM produk WHERE id = ?");
$stmt->execute([$id]);
$produk = $stmt->fetch();

// Jika produk tidak ditemukan, kembali ke daftar
if (!$produk) {
    header('Location: index.php');
    exit();
}

// Ambil kategori untuk dropdown
$kategoriList = $pdo->query("SELECT * FROM kategori ORDER BY nama_kategori")->fetchAll();

$errors = [];
$form = $produk; // Isi form dengan data saat ini

// ============================================
// PROSES UPDATE (saat form di-submit)
// ============================================
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    $form = [
        'kode_produk'  => trim($_POST['kode_produk'] ?? ''),
        'nama_produk'  => trim($_POST['nama_produk'] ?? ''),
        'harga_beli'   => $_POST['harga_beli'] ?? 0,
        'harga_jual'   => $_POST['harga_jual'] ?? 0,
        'stok'         => $_POST['stok'] ?? 0,
        'id_kategori'  => $_POST['id_kategori'] ?: null,
    ];
    
    // Validasi
    if (empty($form['kode_produk'])) $errors[] = 'Kode produk harus diisi!';
    if (empty($form['nama_produk'])) $errors[] = 'Nama produk harus diisi!';
    if ($form['harga_jual'] <= 0)   $errors[] = 'Harga jual harus lebih dari 0!';
    
    // Cek kode duplikat (tapi izinkan kode yang sama milik produk ini)
    if (empty($errors)) {
        $cek = $pdo->prepare(
            "SELECT id FROM produk WHERE kode_produk = ? AND id != ?"
        );
        $cek->execute([$form['kode_produk'], $id]);
        if ($cek->fetch()) {
            $errors[] = 'Kode produk sudah digunakan produk lain!';
        }
    }
    
    // Simpan perubahan
    if (empty($errors)) {
        $sql = "UPDATE produk SET
                    kode_produk = :kode,
                    nama_produk = :nama,
                    harga_beli  = :hbeli,
                    harga_jual  = :hjual,
                    stok        = :stok,
                    id_kategori = :kategori
                WHERE id = :id";
        
        $stmt = $pdo->prepare($sql);
        $stmt->execute([
            ':kode'     => $form['kode_produk'],
            ':nama'     => $form['nama_produk'],
            ':hbeli'    => $form['harga_beli'],
            ':hjual'    => $form['harga_jual'],
            ':stok'     => $form['stok'],
            ':kategori' => $form['id_kategori'],
            ':id'       => $id,
        ]);
        
        header('Location: index.php?pesan=edit');
        exit();
    }
}
?>

<div class="row justify-content-center">
    <div class="col-lg-8">
        <div class="d-flex align-items-center gap-3 mb-4">
            <a href="index.php" class="btn btn-outline-secondary btn-sm">
                <i class="fas fa-arrow-left"></i>
            </a>
            <h4 class="fw-bold mb-0">✏️ Edit Produk</h4>
        </div>
        
        <?php if (!empty($errors)): ?>
            <div class="alert alert-danger">
                <ul class="mb-0">
                    <?php foreach ($errors as $err): ?>
                        <li><?= htmlspecialchars($err) ?></li>
                    <?php endforeach; ?>
                </ul>
            </div>
        <?php endif; ?>
        
        <div class="card border-0 shadow-sm">
            <div class="card-body p-4">
                <form method="POST" action="">
                    <div class="row g-3">
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">Kode Produk *</label>
                            <input type="text" name="kode_produk" class="form-control"
                                   value="<?= htmlspecialchars($form['kode_produk']) ?>" required>
                        </div>
                        <div class="col-md-8">
                            <label class="form-label fw-semibold">Nama Produk *</label>
                            <input type="text" name="nama_produk" class="form-control"
                                   value="<?= htmlspecialchars($form['nama_produk']) ?>" required>
                        </div>
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">Kategori</label>
                            <select name="id_kategori" class="form-select">
                                <option value="">-- Pilih --</option>
                                <?php foreach ($kategoriList as $kat): ?>
                                    <option value="<?= $kat['id'] ?>"
                                        <?= ($form['id_kategori'] == $kat['id']) ? 'selected' : '' ?>>
                                        <?= htmlspecialchars($kat['nama_kategori']) ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                        <div class="col-md-4">
                            <label class="form-label fw-semibold">Stok</label>
                            <input type="number" name="stok" class="form-control"
                                   value="<?= $form['stok'] ?>" min="0">
                        </div>
                        <div class="col-md-6">
                            <label class="form-label fw-semibold">Harga Beli</label>
                            <div class="input-group">
                                <span class="input-group-text">Rp</span>
                                <input type="number" name="harga_beli" class="form-control"
                                       value="<?= $form['harga_beli'] ?>" min="0">
                            </div>
                        </div>
                        <div class="col-md-6">
                            <label class="form-label fw-semibold">Harga Jual *</label>
                            <div class="input-group">
                                <span class="input-group-text">Rp</span>
                                <input type="number" name="harga_jual" class="form-control"
                                       value="<?= $form['harga_jual'] ?>" min="1" required>
                            </div>
                        </div>
                    </div>
                    <div class="d-flex gap-2 mt-4">
                        <button type="submit" class="btn btn-warning px-4">
                            <i class="fas fa-save me-2"></i>Simpan Perubahan
                        </button>
                        <a href="index.php" class="btn btn-outline-secondary">Batal</a>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

<?php require_once '../includes/footer.php'; ?>
5. DELETE — Hapus Produk (produk/hapus.php)
DELETE FROM produk WHERE id = ?
📄 produk/hapus.php DELETE
<?php
// ============================================
// FILE: produk/hapus.php
// Proses hapus produk (DELETE)
// File ini TIDAK punya tampilan UI
// Hanya proses dan redirect
// ============================================

session_start();
require_once '../config/database.php';

// Ambil ID dari URL: hapus.php?id=5
$id = (int)($_GET['id'] ?? 0);

// Validasi: ID harus valid
if ($id <= 0) {
    header('Location: index.php');
    exit();
}

// Cek apakah produk ada
$cek = $pdo->prepare("SELECT id, nama_produk FROM produk WHERE id = ?");
$cek->execute([$id]);
$produk = $cek->fetch();

if ($produk) {
    // Hapus produk
    $stmt = $pdo->prepare("DELETE FROM produk WHERE id = ?");
    $stmt->execute([$id]);
    
    // Redirect dengan pesan sukses
    header('Location: index.php?pesan=hapus');
} else {
    // Produk tidak ditemukan
    header('Location: index.php');
}

exit();
?>
⚠️ Catatan Penting DELETE:
  • Selalu konfirmasi sebelum hapus (JavaScript confirm() di tombol)
  • Pastikan validasi ID di server-side (jangan hanya di JavaScript!)
  • Pertimbangkan soft delete untuk data penting (tambah kolom deleted_at daripada benar-benar hapus)
  • Produk yang sudah ada di transaksi tidak perlu dihapus, cukup nonaktifkan
6. File Footer: includes/footer.php
📄 includes/footer.php
<!-- Footer dipanggil di akhir setiap halaman -->

</div><!-- End container-fluid -->

<footer class="bg-white border-top mt-5 py-3 text-center">
    <small class="text-muted">
        Sistem POS © <?= date('Y') ?> — Logged in as 
        <strong><?= htmlspecialchars($_SESSION['user_nama'] ?? '') ?></strong>
    </small>
</footer>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>