Dokumentasi

Panduan Lengkap
POS Restoran

Tutorial langkah demi langkah dari setup awal sampai operasional harian. Pelajari semua fitur dan cara mengelola bisnis restoran Anda dengan POS Restoran.

Akun Demo

Login untuk eksplorasi semua fitur

Role Email Password Cakupan
Superadmin [email protected] password Akses penuh semua menu admin & SaaS panel
Data dummy — bebas eksplorasi semua fitur tanpa takut merusak data.
Login Admin

Tutorial Langkah Demi Langkah

Ikuti 5 fase tutorial ini untuk menguasai POS Restoran dari nol sampai mahir. Setiap fase mencakup langkah-langkah esensial sesuai alur bisnis restoran.

Fase 1 5 langkah

Setup Awal

Registrasi akun, konfigurasi outlet, dan atur staff untuk pertama kali.

1

Langkah 1.1: Daftar Akun

Buka halaman login, klik "Daftar gratis". Isi nama restoran, email, password. Akun langsung aktif.

2

Langkah 1.2: Setup Outlet

Setelah login, buka menu Pengaturan > Outlet. Isi nama outlet, alamat, jam operasional, pajak restoran (PB1), dan service charge. Satu akun bisa punya banyak outlet.

3

Langkah 1.3: Atur Staff & Role

Tambah staff (kasir, chef, manager) di menu Pengaturan > Staff. Setiap staff punya role dengan izin berbeda: kasir hanya bisa transaksi, manager bisa lihat laporan, chef akses antrian dapur.

4

Langkah 1.4: Setup Meja (Opsional)

Jika restoran Anda pakai sistem meja, buat denah meja di menu Pengaturan > Meja. Tentukan nomor meja, kapasitas, dan zona (indoor/outdoor).

5

Langkah 1.5: Konfigurasi Printer

Hubungkan printer thermal Bluetooth/USB untuk cetak struk. Bisa atur multiple printer: satu di kasir, satu di dapur.

Fase 2 5 langkah

Master Data

Input menu, bahan baku, dan resep — fondasi data bisnis Anda.

1

Langkah 2.1: Input Kategori Menu

Buat kategori menu: Makanan, Minuman, Snack, Dessert. Kategori membantu pengelompokan di POS dan laporan.

2

Langkah 2.2: Input Menu

Tambah menu makanan & minuman lengkap: nama, foto, harga jual, kategori, dan varian (level pedas, ukuran, topping). Upload foto dari HP langsung.

3

Langkah 2.3: Input Bahan Baku

Daftarkan semua bahan baku: beras, ayam, minyak, bumbu, dll. Isi satuan (kg/gram/liter/pcs), harga beli per satuan, dan stok awal.

4

Langkah 2.4: Buat Resep & Hitung HPP

Buat resep untuk setiap menu: tentukan bahan baku yang dipakai + takarannya. Sistem otomatis hitung HPP (Harga Pokok Penjualan) dan food cost percentage.

5

Langkah 2.5: Stok Opname Awal

Setelah semua bahan baku masuk, lakukan stok opname awal untuk memastikan stok fisik = stok sistem. Ini penting untuk akurasi laporan ke depan.

Fase 3 5 langkah

Operasional Harian

Buka shift, transaksi POS, dan kelola antrian dapur setiap hari.

1

Langkah 3.1: Buka Shift Kasir

Setiap awal shift, kasir buka shift dengan input saldo awal kas. Sistem mencatat semua transaksi dalam shift tersebut.

2

Langkah 3.2: Transaksi POS

Kasir input pesanan pelanggan: pilih menu, tentukan meja (jika ada), tambah catatan khusus, terapkan diskon/voucher. Sistem otomatis kurangi stok bahan baku.

3

Langkah 3.3: Pembayaran

Pilih metode bayar: tunai, QRIS, kartu debit/kredit, atau split bill. Cetak struk. Sistem catat semua pembayaran untuk rekonsiliasi.

4

Langkah 3.4: Antrian Dapur

Setelah pesanan dibayar, otomatis muncul di layar dapur (kitchen display). Chef lihat urutan pesanan, proses, lalu tandai "selesai" saat makanan siap saji.

5

Langkah 3.5: Tutup Shift & Rekap Kas

Di akhir shift, kasir tutup shift. Sistem hitung total transaksi tunai, non-tunai, diskon, pajak. Bandingkan dengan fisik uang di laci.

Fase 4 5 langkah

Monitoring & Laporan

Pantau stok, cek laporan penjualan, dan analisis performa bisnis.

1

Langkah 4.1: Cek Stok Harian

Dashboard menampilkan stok menipis dengan warna merah. Filter per kategori bahan baku. Sistem kirim notifikasi saat stok di bawah batas minimum.

2

Langkah 4.2: Stok Opname Berkala

Lakukan stok opname mingguan/bulanan. Bandingkan stok fisik vs stok sistem. Catat selisih dan adjustment untuk menjaga akurasi data.

3

Langkah 4.3: Laporan Penjualan

Lihat laporan penjualan harian/mingguan/bulanan. Filter per outlet, per kategori, per menu. Lihat tren, best seller, dan worst seller. Export Excel & PDF.

4

Langkah 4.4: Laporan Laba Rugi

Sistem hitung otomatis: total penjualan - HPP = laba kotor. Kurangi biaya operasional untuk dapat laba bersih. Lihat margin per menu.

5

Langkah 4.5: Laporan Stok & Pembelian

Pantau pergerakan stok: barang masuk, barang keluar (terpakai), stok akhir. Rekomendasi pembelian bahan baku berdasarkan usage history.

Fase 5 5 langkah

Admin SaaS

Untuk pemilik SaaS — kelola tenant, subscription, dan monetisasi.

1

Langkah 5.1: Kelola Tenant

Lihat semua restoran yang terdaftar. Filter by plan (free/basic/pro), status aktif/trial/expired. Bisa suspend atau aktifkan tenant kapan saja.

2

Langkah 5.2: Buat & Atur Plan

Buat plan subscription dinamis: tentukan harga, max menu, max outlet, max staff, fitur on/off (export, multi outlet, QR menu, white label). Ubah kapan saja tanpa coding.

3

Langkah 5.3: Pantau Subscription & Revenue

Dashboard SaaS menampilkan MRR, ARR, churn rate, ARPU. Lihat history pembayaran per tenant, status paid/expired, integrasi Tripay.

4

Langkah 5.4: Broadcast & Kupon

Kirim pengumuman massal ke semua tenant via broadcast. Generate kupon diskon untuk akuisisi user baru atau retensi user existing.

5

Langkah 5.5: Feature Flags & Support

Aktifkan/nonaktifkan fitur per tenant via Feature Flags (QR menu, white label, dll). Kelola support ticket dari tenant.

Fitur Lengkap

Semua fitur yang tersedia di POS Restoran, dikelompokkan berdasarkan kategori.

Point of Sale

5 fitur
🖥️

Point of Sale (POS)

Kasir cepat dengan antarmuka intuitif. Dukungan meja, split bill, diskon, voucher, pajak otomatis, dan print Bluetooth/USB.

👨‍🍳

Antrian Dapur

Pesanan otomatis muncul di layar dapur. Chef tahu apa yang harus dimasak, urutannya, dan berapa lama pesanan pending.

🔄

Shift Management

Buka/tutup shift kasir dengan saldo awal. Rekap kas per shift otomatis. Lacak siapa yang bertugas di setiap shift.

📱

Offline Mode

Tetap bisa transaksi meski internet mati. Data tersimpan lokal dan auto-sync ke server saat online kembali.

🛡️

Multi Staff & Role

Atur akses staff: kasir, chef, manager — masing-masing dengan izin berbeda. Full RBAC (Role-Based Access Control).

Manajemen Menu & Bahan

4 fitur
🍽️

Manajemen Menu

Kelola menu makanan & minuman lengkap dengan foto, harga, kategori, varian (level pedas, ukuran, topping), dan status aktif/nonaktif.

🥩

Stok Bahan Baku

Pantau stok real-time, auto-deduct setiap transaksi, notifikasi stok menipis, riwayat penerimaan barang, stok opname berkala.

📋

Resep & HPP

Buat resep per menu dengan bahan baku + takaran. Sistem otomatis hitung HPP (Harga Pokok Penjualan) & food cost percentage per menu.

🏪

Multi Outlet

Kelola banyak cabang dari satu akun. Data terpisah per outlet (menu, stok, staff), dashboard terpusat untuk pemilik.

Pelanggan & Loyalty

3 fitur
👥

Member & Loyalty

Program member dengan poin reward. Setiap transaksi kumpulkan poin yang bisa ditukar diskon. Riwayat pembelian pelanggan tercatat lengkap.

🎫

Kupon & Diskon

Buat kupon diskon (persentase atau nominal). Batasi masa berlaku, minimal pembelian, maksimal pemakaian. Bagikan via broadcast atau manual.

🔔

Notifikasi

Notifikasi real-time: stok menipis, order masuk, pembayaran sukses, plan expired. Via in-app, email, atau WhatsApp (opsional).

Laporan & Analitik

3 fitur
📊

Laporan Penjualan

Laporan penjualan harian/mingguan/bulanan. Filter per outlet, kategori, menu. Tren penjualan, best seller & worst seller. Export Excel & PDF.

💰

Laporan Laba Rugi

Hitung otomatis: total penjualan - HPP = laba kotor. Kurangi biaya = laba bersih. Margin per menu, food cost analysis.

📦

Laporan Stok

Pergerakan stok: barang masuk, keluar (terpakai), stok akhir. Nilai inventori, rekomendasi pembelian, barang kadaluarsa.

Admin & SaaS Panel

9 fitur
👥

Manajemen Users & Tenant

Lihat semua tenant, status plan, history subscription. Suspend/aktifkan tenant. Reset password.

💳

Subscription & Pembayaran

Pantau pembayaran via Tripay, status paid/expired, revenue per bulan. Reminder otomatis sebelum plan expired.

🏷️

Plan Dinamis

Buat & edit plan sesuka hati: harga, limit menu, staff, outlet. Fitur on/off per plan (export, multi outlet, QR menu, white label).

📈

Analytics MRR/ARR

Dashboard SaaS dengan metrik: MRR, ARR, churn rate, ARPU, LTV, conversion rate, trial-to-paid rate.

📢

Broadcast

Kirim pengumuman massal ke semua tenant atau filter per plan. Track delivery status.

🔧

Feature Flags

Aktifkan/nonaktifkan fitur per tenant tanpa deploy: QR menu, white label, export, API access, custom domain.

🛡️

Multi Admin & Audit Log

Superadmin, admin, support — RBAC penuh. Audit log mencatat semua aksi admin: siapa melakukan apa, kapan.

🎫

Support Ticket

Sistem ticket untuk tenant melapor bug atau minta bantuan. Admin reply, update status, resolusi.

✉️

Email Automation

Email otomatis: welcome email saat daftar, reminder sebelum expiry, notifikasi pembayaran sukses/gagal.

📖 Cara Penggunaan

Panduan praktis menggunakan Laravel admin panel & Flutter POS app sehari-hari.

Akses Laravel Admin Panel

▸ Admin panel Laravel digunakan untuk mengelola SaaS: tenant, subscription, plan, broadcast, kupon, analytics.
▸ Buka /login di browser, masuk dengan akun demo di atas.
▸ Dashboard menampilkan: total user, revenue bulan ini, new users, user expired / expiring soon.
▸ Menu User: lihat & kelola semua tenant restoran yang terdaftar, filter plan & status, edit plan tenant.
▸ Menu Subscription: pantau history pembayaran Tripay, status paid/expired/failed.
▸ Menu Plan: buat, edit, hapus plan subscription dengan harga & limit fitur dinamis.
▸ Menu Broadcast: kirim pengumuman massal ke semua tenant atau filter per plan.
▸ Menu Kupon: generate kupon diskon, toggle aktif/nonaktif.
▸ Menu Analytics: lihat MRR, ARR, ARPU, churn rate, conversion rate dalam grafik.
▸ Menu Feature Flags: aktifkan/nonaktifkan fitur per tenant (QR menu, white label, API access, dll).
▸ Menu Multi Admin: tambah admin baru dengan role superadmin/admin/support.
▸ Menu Support Ticket: kelola tiket bantuan dari tenant, reply & update status.
▸ Menu Email Automation: kirim email selamat datang, reminder expiry, atau test email.
▸ Menu Audit Log: lacak semua aksi admin — siapa, apa, kapan.
▸ Menu Settings: atur konfigurasi aplikasi (nama, logo, pajak default, dll).

Akses Flutter POS App

▸ Aplikasi Flutter adalah aplikasi POS yang dipakai pemilik restoran sehari-hari.
▸ Buka pos.balaspro.com di browser (web) atau install APK di Android.
▸ Login pakai email & password yang sudah didaftarkan via admin panel atau register langsung.
▸ Setelah login, Anda masuk ke Dashboard dengan ringkasan penjualan hari ini.
▸ Tab Kasir (POS): tempat transaksi utama — pilih menu, tentukan meja, terapkan diskon/voucher, pilih metode bayar (tunai/QRIS/kartu), cetak struk.
▸ Tab Menu: kelola menu makanan & minuman — tambah, edit, hapus, upload foto, atur harga, kategori, varian.
▸ Tab Bahan Baku: kelola stok bahan baku — tambah, edit stok, lihat stok menipis, stok opname.
▸ Resep & HPP (dari More Menu): buat resep per menu — pilih bahan baku & takaran, sistem hitung HPP otomatis.
▸ Antrian Dapur (More Menu): layar terpisah untuk chef — lihat pesanan masuk, urutan, tandai selesai.
▸ Laporan: lihat laporan penjualan harian/mingguan/bulanan, laba rugi, stok — export Excel & PDF.
▸ Shift Management: buka shift (input saldo awal) → transaksi → tutup shift (rekap kas).
▸ Member & Loyalty: daftarkan pelanggan, kumpulkan poin, tukar reward.
▸ Multi Outlet: kelola beberapa cabang restoran dari satu akun.
▸ Staff Management: tambah staff (kasir, chef, manager) dengan izin berbeda.
▸ Offline Mode: tetap bisa transaksi walau internet mati, data auto-sync saat online.
▸ Bluetooth Print: cetak struk langsung ke printer thermal via Bluetooth.
▸ Upgrade Plan: lihat plan yang tersedia, upgrade langsung dari aplikasi.
▸ Support Ticket: kirim tiket bantuan jika ada kendala.

🗄️ Setup Supabase

Cara membuat project Supabase, menjalankan SQL schema, dan menghubungkan ke Flutter.

📂

Cara Install Cepat

File SQL sudah disediakan di folder supabase/

full-schema.sql
18 tabel + RLS policy + RPC function. Copy seluruh isi → paste di SQL Editor Supabase → Run.
storage-bucket.sql
Setup bucket menu-images untuk upload gambar menu & logo restoran.
▸ Clone atau download project ini, buka folder supabase/.
▸ Baca supabase/README.md untuk panduan lengkap.
▸ Buka supabase.com → Sign In → New Project → pilih region Singapore.
▸ Buka SQL Editor → copy seluruh isi supabase/full-schema.sql → Run.
▸ Jalankan juga supabase/storage-bucket.sql untuk setup bucket gambar.
▸ Buka Project Settings → API — catat Project URL dan anon public key.
▸ Buka Authentication → URL Configuration → Site URL: domain Flutter Anda. Redirect URLs tambahkan https://domain-anda.com/**.
▸ Update lib/main.dart: supabaseUrl + supabaseAnonKey.
▸ Update lib/services/supabase_service.dart: laravelBaseUrl + apiSharedKey.
▸ Build Flutter: flutter build web → upload build/web/ ke server.

SQL Schema — Supabase

Copy-paste satu per satu di SQL Editor Supabase. Total 18 tabel + 1 RPC function.

Profil restoran per user profiles
CREATE TABLE profiles (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
    restaurant_name TEXT NOT NULL DEFAULT 'Restoran Saya',
    address TEXT,
    phone TEXT,
    receipt_note TEXT DEFAULT 'Terima kasih telah berkunjung!',
    logo_url TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY profiles_user_policy ON profiles
    FOR ALL USING (auth.uid() = user_id);
Diskon & promo promos
CREATE TABLE promos (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    type TEXT NOT NULL DEFAULT 'percentage' CHECK (type IN ('percentage','fixed')),
    value NUMERIC(12,2) NOT NULL,
    min_purchase NUMERIC(12,2) NOT NULL DEFAULT 0,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE promos ENABLE ROW LEVEL SECURITY;
CREATE POLICY promos_user_policy ON promos FOR ALL USING (auth.uid() = user_id);
Meja restoran tables
CREATE TABLE tables (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    capacity INTEGER NOT NULL DEFAULT 4,
    status TEXT NOT NULL DEFAULT 'available' CHECK (status IN ('available','occupied','reserved')),
    current_order_id UUID,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE tables ENABLE ROW LEVEL SECURITY;
CREATE POLICY tables_user_policy ON tables FOR ALL USING (auth.uid() = user_id);
Bahan baku ingredients
CREATE TABLE ingredients (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    unit TEXT NOT NULL DEFAULT 'gram',
    stock NUMERIC(12,3) NOT NULL,
    min_stock NUMERIC(12,3) NOT NULL DEFAULT 0,
    cost_per_unit NUMERIC(12,2) NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE ingredients ENABLE ROW LEVEL SECURITY;
CREATE POLICY ingredients_user_policy ON ingredients FOR ALL USING (auth.uid() = user_id);
Menu makanan & minuman menus
CREATE TABLE menus (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    category TEXT NOT NULL DEFAULT 'Makanan',
    price NUMERIC(12,2) NOT NULL,
    image_url TEXT,
    is_available BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE menus ENABLE ROW LEVEL SECURITY;
CREATE POLICY menus_user_policy ON menus FOR ALL USING (auth.uid() = user_id);
Resep per menu (HPP) recipes
CREATE TABLE recipes (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    menu_id UUID NOT NULL REFERENCES menus(id) ON DELETE CASCADE,
    ingredient_id UUID NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE,
    quantity NUMERIC(12,3) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE recipes ENABLE ROW LEVEL SECURITY;
CREATE POLICY recipes_user_policy ON recipes FOR ALL
    USING (EXISTS (SELECT 1 FROM menus WHERE menus.id = recipes.menu_id AND menus.user_id = auth.uid()));
Transaksi penjualan transactions
CREATE TABLE transactions (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    total_amount NUMERIC(12,2) NOT NULL,
    payment_method TEXT NOT NULL DEFAULT 'cash',
    amount_paid NUMERIC(12,2) NOT NULL,
    change_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
    discount_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
    discount_type TEXT NOT NULL DEFAULT 'none',
    discount_value NUMERIC(12,2) NOT NULL DEFAULT 0,
    payment_details JSONB NOT NULL DEFAULT '[]'::jsonb,
    notes TEXT,
    table_id UUID REFERENCES tables(id) ON DELETE SET NULL,
    member_id UUID REFERENCES members(id) ON DELETE SET NULL,
    outlet_id UUID REFERENCES outlets(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_transactions_user_date ON transactions(user_id, created_at DESC);

ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY transactions_user_policy ON transactions FOR ALL USING (auth.uid() = user_id);
Item per transaksi transaction_items
CREATE TABLE transaction_items (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    transaction_id UUID NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
    menu_id UUID NOT NULL REFERENCES menus(id) ON DELETE SET NULL,
    menu_name TEXT NOT NULL,
    quantity INTEGER NOT NULL,
    unit_price NUMERIC(12,2) NOT NULL,
    subtotal NUMERIC(12,2) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE transaction_items ENABLE ROW LEVEL SECURITY;
CREATE POLICY transaction_items_user_policy ON transaction_items FOR ALL
    USING (EXISTS (SELECT 1 FROM transactions WHERE transactions.id = transaction_items.transaction_id AND transactions.user_id = auth.uid()));
Retur / refund returns
CREATE TABLE returns (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    transaction_id UUID NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
    reason TEXT NOT NULL,
    total_amount NUMERIC(12,2) NOT NULL,
    status TEXT NOT NULL DEFAULT 'approved',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE returns ENABLE ROW LEVEL SECURITY;
CREATE POLICY returns_user_policy ON returns FOR ALL USING (auth.uid() = user_id);
Antrian dapur order_queues
CREATE TABLE order_queues (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    queue_number INTEGER NOT NULL,
    table_name TEXT,
    customer_notes TEXT,
    items JSONB NOT NULL DEFAULT '[]'::jsonb,
    status TEXT NOT NULL DEFAULT 'waiting' CHECK (status IN ('waiting','cooking','ready','done')),
    updated_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_order_queues_active ON order_queues(user_id, status) WHERE status != 'done';

ALTER TABLE order_queues ENABLE ROW LEVEL SECURITY;
CREATE POLICY order_queues_user_policy ON order_queues FOR ALL USING (auth.uid() = user_id);
Shift kasir shifts
CREATE TABLE shifts (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    opening_cash NUMERIC(12,2) NOT NULL,
    status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed')),
    opened_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    closing_cash NUMERIC(12,2),
    total_sales NUMERIC(12,2),
    closed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE shifts ENABLE ROW LEVEL SECURITY;
CREATE POLICY shifts_user_policy ON shifts FOR ALL USING (auth.uid() = user_id);
Kupon / voucher diskon vouchers
CREATE TABLE vouchers (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    code TEXT NOT NULL,
    type TEXT NOT NULL CHECK (type IN ('percentage','fixed')),
    value NUMERIC(12,2) NOT NULL,
    min_purchase NUMERIC(12,2) NOT NULL DEFAULT 0,
    max_usage INTEGER NOT NULL DEFAULT 1,
    is_active BOOLEAN NOT NULL DEFAULT true,
    expired_at TIMESTAMPTZ,
    used_count INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE vouchers ENABLE ROW LEVEL SECURITY;
CREATE POLICY vouchers_user_policy ON vouchers FOR ALL USING (auth.uid() = user_id);

-- RPC untuk atomic increment used_count
CREATE OR REPLACE FUNCTION increment_voucher_usage(voucher_code TEXT, tenant_id UUID)
RETURNS void AS $$
BEGIN
    UPDATE vouchers SET used_count = used_count + 1
    WHERE code = voucher_code AND user_id = tenant_id AND is_active = true;
END;
$$ LANGUAGE plpgsql;
Member loyalty members
CREATE TABLE members (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    phone TEXT NOT NULL,
    email TEXT,
    points INTEGER NOT NULL DEFAULT 0,
    total_spent NUMERIC(12,2) NOT NULL DEFAULT 0,
    tier TEXT NOT NULL DEFAULT 'regular' CHECK (tier IN ('regular','silver','gold')),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE members ENABLE ROW LEVEL SECURITY;
CREATE POLICY members_user_policy ON members FOR ALL USING (auth.uid() = user_id);
Multi outlet / cabang outlets
CREATE TABLE outlets (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    address TEXT,
    phone TEXT,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE outlets ENABLE ROW LEVEL SECURITY;
CREATE POLICY outlets_user_policy ON outlets FOR ALL USING (auth.uid() = user_id);
Staff / kasir (owner_id = user_id pemilik) staff
CREATE TABLE staff (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    email TEXT,
    pin TEXT NOT NULL,
    role TEXT NOT NULL DEFAULT 'kasir' CHECK (role IN ('kasir','supervisor')),
    outlet_id UUID REFERENCES outlets(id) ON DELETE SET NULL,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE staff ENABLE ROW LEVEL SECURITY;
CREATE POLICY staff_user_policy ON staff FOR ALL USING (auth.uid() = owner_id);
Notifikasi in-app notifications
CREATE TABLE notifications (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    message TEXT NOT NULL,
    is_read BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE notifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY notifications_user_policy ON notifications FOR ALL USING (auth.uid() = user_id);
Tiket bantuan (tenant_id dari Laravel API) support_tickets
CREATE TABLE support_tickets (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    tenant_id INTEGER NOT NULL,
    subject TEXT NOT NULL,
    message TEXT NOT NULL,
    priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low','medium','high','urgent')),
    status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','resolved','closed')),
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Balasan tiket support ticket_replies
CREATE TABLE ticket_replies (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    ticket_id UUID NOT NULL REFERENCES support_tickets(id) ON DELETE CASCADE,
    sender TEXT NOT NULL CHECK (sender IN ('admin','user')),
    admin_email TEXT,
    message TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
File yang perlu diupdate di Flutter project Konfigurasi Flutter
// File: lib/main.dart (baris ~12-13)
const String supabaseUrl = 'https://XXXX.supabase.co';
const String supabaseAnonKey = 'eyJhbGciOi...';

// File: lib/services/supabase_service.dart (baris ~15-20)
const String laravelBaseUrl = 'https://manage.DOMAIN-ANDA.com/api';
const String apiSharedKey = 'SHARED-KEY-DARI-ENV-LARAVEL';
const Map<String, String> apiHeaders = {
    'Content-Type': 'application/json',
    'X-API-Key': apiSharedKey,
};

🚀 Deploy Flutter ke Domain Sendiri

Cara build & deploy Flutter web ke VPS, aaPanel, atau Firebase Hosting dengan domain kustom.

Build Flutter Web

▸ Pastikan Flutter SDK sudah terinstall di komputer Anda.
▸ Buka terminal di folder project Flutter (saas project/).
▸ Jalankan flutter build web — ini akan menghasilkan folder build/web/.
▸ Output build ada di build/web/ — folder ini yang akan di-upload ke server.

Build Android APK / AAB

▸ APK (debug/testing): flutter build apk --debug — output di build/app/outputs/flutter-apk/app-debug.apk.
▸ APK (release): flutter build apk --release — lebih kecil, optimized.
▸ AAB (Play Store): flutter build appbundle — output di build/app/outputs/bundle/release/app-release.aab.
▸ Generate keystore (sekali): keytool -genkey -v -keystore android/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload.
▸ Isi file android/key.properties dengan password & alias yang dipakai saat generate keystore.
▸ File key.properties dan *.jks sudah di-exclude dari git — aman.
▸ Upload AAB ke Google Play Console untuk publish.

Deploy ke aaPanel / Nginx

▸ Di aaPanel, buat website baru dengan domain yang diinginkan (contoh: pos.restoran-anda.com).
▸ Upload semua isi folder build/web/ ke root folder website.
▸ Buka pengaturan website → Configuration File, ganti isinya dengan Nginx config berikut:
▸
server {
    listen 80;
    server_name pos.restoran-anda.com;
    root /www/wwwroot/pos.restoran-anda.com;

    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}
▸ Simpan konfigurasi dan restart Nginx.
▸ Jangan lupa tambahkan SSL (Let's Encrypt) untuk HTTPS.

Konfigurasi Supabase Auth Redirect

▸ Buka dashboard Supabase → Authentication → URL Configuration.
▸ Di field Site URL, isi dengan domain baru Anda (contoh: https://pos.restoran-anda.com).
▸ Di Redirect URLs, tambahkan https://pos.restoran-anda.com/**.
▸ Jika tidak diupdate, login Supabase akan error karena redirect URL tidak cocok.

Ganti URL Laravel API di Flutter

▸ File: lib/services/supabase_service.dart — baris 3:
▸
// Ganti dari:
const String laravelBaseUrl = 'https://manage.balaspro.com/api';

// Menjadi:
const String laravelBaseUrl = 'https://DOMAIN-ANDA.com/api';
▸ File: lib/services/supabase_service.dart — baris 4 (API Shared Key):
▸
// Ganti dengan key dari .env ADMIN_API_SHARED_KEY
const String apiSharedKey = 'YOUR_SHARED_KEY';
▸ Setelah ganti, rebuild Flutter web: flutter build web dan upload ulang build/web/.

Deploy Flutter ke Firebase Hosting (Alternatif)

▸ Install Firebase CLI: npm install -g firebase-tools
▸ Login: firebase login
▸ Init: firebase init hosting → pilih project Firebase Anda → public directory: build/web → SPA: Yes.
▸ Build: flutter build web
▸ Deploy: firebase deploy --only hosting
▸ Flutter app akan live di https://PROJECT-ID.web.app dan https://PROJECT-ID.firebaseapp.com.
▸ Untuk custom domain, tambahkan di Firebase Console → Hosting → Add custom domain.
▸ File firebase.json dan .firebaserc sudah ada di project — tinggal sesuaikan project ID.

Siap Mulai dengan POS Restoran?

Daftar gratis, setup 2 menit, langsung bisa transaksi. Upgrade kapan saja sesuai kebutuhan bisnis Anda.