Cara Membuat Slot Booking Otomatis Berdasarkan Durasi Layanan di PHP & MySQL (Studi Kasus Barbershop)
Booking online untuk barbershop, salon, atau klinik kecantikan punya satu tantangan utama: jam mana saja yang masih bisa dipesan? Jawabannya bergantung pada banyak hal sekaligus: durasi layanan (potong rambut 30 menit, coloring 90 menit), jam kerja kapster, hari libur, jadwal yang sudah terisi, dan jam yang sudah lewat.
Di tutorial ini kita bangun fungsi slot booking otomatis dengan PHP & MySQL, lalu mengamankannya dari double booking. Contohnya diambil dari aplikasi Booking & Antrean Barbershop yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Simpan waktu dalam menit
Membandingkan jam dalam bentuk string ("10:30") merepotkan. Lebih mudah menyimpan menit sejak tengah malam:
| Jam | Menit |
|---|---|
| 10.00 | 600 |
| 10.30 | 630 |
| 13.15 | 795 |
CREATE TABLE kapster (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
jam_mulai TIME NOT NULL DEFAULT '10:00:00',
jam_selesai TIME NOT NULL DEFAULT '21:00:00',
hari_libur VARCHAR(20) NOT NULL DEFAULT '' -- 0=Minggu ... 6=Sabtu, contoh "1" = libur Senin
) ENGINE=InnoDB;
CREATE TABLE kunjungan (
id INT AUTO_INCREMENT PRIMARY KEY,
kapster_id INT NOT NULL,
tanggal DATE NOT NULL,
mulai SMALLINT NOT NULL, -- menit, contoh 630 = 10.30
selesai SMALLINT NOT NULL, -- mulai + durasi layanan
status ENUM('booking','menunggu','dilayani','selesai','batal','tidak_datang') NOT NULL,
INDEX (kapster_id, tanggal)
) ENGINE=InnoDB;
Dua fungsi bantu untuk konversi:
<?php
function jam_ke_menit(string $hhmm): int
{
[$h, $m] = array_map('intval', explode(':', substr($hhmm, 0, 5)));
return $h * 60 + $m;
}
function menit_ke_jam(int $m): string
{
return sprintf('%02d.%02d', intdiv($m, 60), $m % 60); // 630 -> "10.30"
}
2. Algoritma slot tersedia
Langkahnya:
- Kalau tanggal itu hari libur kapster, tidak ada slot.
- Buat kandidat slot setiap 30 menit dari jam buka.
- Buang kandidat yang selesainya melewati jam tutup (
mulai + durasi > tutup). - Buang kandidat yang sudah lewat (untuk hari ini, beri jeda 15 menit untuk persiapan).
- Buang kandidat yang tumpang tindih dengan kunjungan lain.
Dua rentang [a1, a2) dan [b1, b2) bertumpuk jika a1 < b2 && a2 > b1.
<?php
function slot_tersedia(PDO $pdo, array $kapster, string $tanggal, int $durasi): array
{
// 1. Hari libur
$libur = array_filter(explode(',', $kapster['hari_libur']), 'strlen');
if (in_array(date('w', strtotime($tanggal)), $libur, true)) return [];
$buka = jam_ke_menit($kapster['jam_mulai']);
$tutup = jam_ke_menit($kapster['jam_selesai']);
// Jadwal yang sudah terisi (yang batal & tidak datang tidak memakan slot)
$st = $pdo->prepare("SELECT mulai, selesai FROM kunjungan
WHERE kapster_id = ? AND tanggal = ? AND status NOT IN ('batal','tidak_datang')");
$st->execute([$kapster['id'], $tanggal]);
$terpakai = ->(PDO::);
= === () ? () () * + () () + : ;
= [];
( = ; + <= ; += ) {
( < ) ;
( ) {
( < [] && + > []) ;
}
[] = ;
}
;
}
continue 2 langsung melompat ke kandidat slot berikutnya begitu ditemukan satu bentrokan.
Contoh
Kapster buka 10.00 sampai 21.00, sudah ada booking 10.00–10.45 (Signature Fade). Pelanggan baru ingin Haircut 30 menit:
- 10.00: bentrok (10.00 < 10.45 dan 10.30 > 10.00)
- 10.30: bentrok (10.30 < 10.45 dan 11.00 > 10.00)
- 11.00: tersedia
Untuk layanan Coloring 90 menit, slot 20.00 tidak muncul karena selesainya 21.30, melewati jam tutup.
3. Cegah dua orang memesan jam yang sama
Antara pelanggan melihat daftar jam dan menekan tombol konfirmasi, bisa saja orang lain sudah memesan jam yang sama. Solusinya: kunci baris kapster dalam transaksi, lalu hitung ulang slot sebelum menyimpan.
<?php
$pdo->beginTransaction();
try {
// Request lain untuk kapster yang sama akan menunggu di sini
$pdo->prepare('SELECT id FROM kapster WHERE id = ? FOR UPDATE')->execute([$kapster['id']]);
if (!in_array($mulai, slot_tersedia($pdo, $kapster, $tanggal, $durasi), true)) {
throw new RuntimeException('Maaf, jam tersebut baru saja terisi. Pilih jam lain.');
}
$pdo->prepare("INSERT INTO kunjungan (kapster_id, tanggal, mulai, selesai, status) VALUES (?, ?, ?, ?, 'booking')")
->execute([$kapster['id'], $tanggal, $mulai, $mulai + $durasi]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
echo $e->getMessage();
}
Jangan pernah percaya nilai mulai dari form begitu saja: pelanggan bisa mengubahnya lewat DevTools. Dengan validasi in_array(... slot_tersedia(...)), hanya jam yang memang kosong yang diterima.
4. Bonus: walk-in ke kapster yang paling cepat kosong
Pelanggan yang datang langsung biasanya tidak peduli siapa kapsternya, yang penting cepat. Cukup hitung slot pertama setiap kapster lalu ambil yang paling awal:
<?php
$terbaik = null;
foreach ($kapsterAktif as $k) {
$s = slot_tersedia($pdo, $k, date('Y-m-d'), $durasi);
if ($s && ($terbaik === null || $s[0] < $terbaik['mulai'])) {
$terbaik = ['kapster' => $k, 'mulai' => $s[0]];
}
}
// $terbaik['kapster'] melayani paling cepat, mulai jam menit_ke_jam($terbaik['mulai'])
Ringkasan
- Simpan jam sebagai menit supaya perbandingan dan perhitungan mudah.
- Slot valid jika masih dalam jam kerja, bukan hari libur, belum lewat, dan tidak tumpang tindih (
a1 < b2 && a2 > b1). - Langkah slot (30 menit) terpisah dari durasi layanan, jadi layanan 45 atau 90 menit tetap bisa dijadwalkan.
- Kunci kapster dengan FOR UPDATE dan hitung ulang slot di dalam transaksi sebelum menyimpan.
Download source code lengkapnya
Semua logika di atas sudah dipakai di Aplikasi Booking & Antrean Barbershop PHP Native & MySQL: booking online dengan slot otomatis, panel antrean live per kursi, walk-in ke kapster tercepat, kasir dan penjualan produk, komisi kapster, nota, timeline jadwal, dan laporan. Gratis dan langsung jalan di XAMPP.
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.