> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quinnsambal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cashier

<style>
  {`
    [class*="max-w-none"][class*="table"] {
      display: block !important;
      overflow-x: auto !important;
      max-width: 100% !important;
      width: 100% !important;
      flex-grow: 0 !important;
    }
    [class*="max-w-none"][class*="table"] > table {
      width: 100% !important;
      max-width: 100% !important;
      table-layout: fixed !important;
    }
    .mermaid {
      max-width: 100% !important;
      overflow-x: auto !important;
    }
    article svg[role="img"] {
      max-width: 100% !important;
      height: auto !important;
    }
    article img, .prose img {
      max-width: 100% !important;
      height: auto !important;
    }
    img[src*="LOGO"], img[src*="logo"] {
      max-width: 120px !important;
      max-height: 40px !important;
      width: auto !important;
      height: auto !important;
      object-fit: contain !important;
    }
    `}
</style>

***

title: "Kasir (Cashier)"
description: "Antarmuka kasir utama untuk memproses transaksi penjualan dengan product grid, cart, pembayaran multi-metode, dan receipt printing."
--------------------------------------------------------------------------------------------------------------------------------------------------

# Kasir (Cashier)

<img src="https://mintcdn.com/quinnofspicy/ny1xnfpEa_OBdv6T/docs/mintlify/screenshots/pos/company-pos-cashier.png?fit=max&auto=format&n=ny1xnfpEa_OBdv6T&q=85&s=fb95bd81a78b6a7827131000c1c6ecf2" alt="Halaman Kasir" width="1920" height="1080" data-path="docs/mintlify/screenshots/pos/company-pos-cashier.png" />

Halaman **Kasir** adalah antarmuka utama untuk memproses transaksi penjualan. Dirancang untuk kecepatan dan efisiensi — kasir bisa memproses transaksi dari scan produk hingga cetak receipt dalam hitungan detik. Halaman ini mendukung **barcode scanning**, **split payment**, **multi-channel**, dan **offline mode**.

## Layout Kasir

```mermaid theme={null}
graph LR
    subgraph "Panel Kiri - Product Grid"
        A[Search Bar]
        B[Category Tabs]
        C[Product Grid]
        C1[Product Card 1]
        C2[Product Card 2]
        C3[Product Card N]
    end
    
    subgraph "Panel Kanan - Cart"
        D[Customer Info]
        E[Cart Items]
        F[Discount & Notes]
        G[Total]
        H[Tombol BAYAR]
    end
    
    A --> C
    B --> C
    C -->|Klik/Scan| E
    E --> G
    G --> H
```

## Akses Halaman

| Metode | Detail |
| - | - |
| **URL** | `/companyposcashier` |
| **Sidebar** | Menu **POS** → **Kasir** |
| **Keyboard** | `Ctrl+Shift+C` |
| **Quick Access** | Widget "Transaksi Baru" di Dashboard |

## Komponen Utama

### 1. Product Grid

Area utama untuk memilih produk yang akan dijual:

| Fitur | Deskripsi |
| - | - |
| **Grid View** | Produk ditampilkan dalam grid dengan gambar, nama, dan harga |
| **Search Bar** | Cari produk berdasarkan nama, SKU, atau barcode |
| **Category Filter** | Tab kategori untuk memfilter produk (Semua, Makanan, Minuman, dll) |
| **Quick Add** | Klik produk untuk langsung menambahkan ke cart |
| **Barcode Scan** | Scan barcode untuk pencarian otomatis (butuh kamera/scanner) |
| **Stock Indicator** | Badge stok tersedia di setiap product card |
| **Price Display** | Harga ditampilkan sesuai channel yang aktif |

**Layout Product Grid per Device:**

| Device | Kolom | Ukuran Card |
| - | - | - |
| Desktop (1920px+) | 5 kolom | 180x200px |
| Laptop (1366px) | 4 kolom | 160x180px |
| Tablet (768px) | 3 kolom | 140x160px |
| Smartphone (375px) | 2 kolom | 120x140px |

### 2. Shopping Cart

Panel di sebelah kanan yang menampilkan item yang dipilih:

| Kolom | Deskripsi |
| - | - |
| **Produk** | Nama produk dengan variant (jika ada) |
| **Qty** | Jumlah — bisa di-adjust dengan tombol +/- atau input manual |
| **Harga** | Harga satuan sesuai channel |
| **Subtotal** | Qty × Harga |
| **Hapus** | Tombol X untuk menghapus item dari cart |

**Fitur Cart:**

* **Real-time Total** — total otomatis update setiap kali ada perubahan
* **Hold & Recall** — tahan transaksi untuk diproses nanti
* **Customer Link** — link transaksi ke customer untuk membership points
* **Notes** — tambahkan catatan per item atau per transaksi

### 3. Tombol BAYAR

Tombol utama di bagian bawah cart untuk memulai proses checkout:

| State | Warna | Deskripsi |
| - | - | - |
| **Empty Cart** | Abu-abu (disabled) | Cart kosong, tidak bisa diklik |
| **Has Items** | Hijau (active) | Ada item di cart, siap checkout |
| **Processing** | Biru (loading) | Sedang memproses pembayaran |

## Modal Pembayaran

Setelah klik tombol BAYAR, modal pembayaran muncul dengan komponen:

### Payment Methods

| Metode | Input | Verifikasi |
| - | - | - |
| **Cash** | Nominal uang diterima | Otomatis, hitung kembalian |
| **Transfer** | Pilih rekening tujuan | Manual (upload bukti) |
| **QRIS** | Generate QR code | Otomatis |
| **Debit** | Swipe/insert card | Otomatis |
| **Credit** | Swipe/insert card | Otomatis |
| **E-Wallet** | Scan QR / input nomor | Otomatis |

### Sales Channel

Pilih channel penjualan yang aktif:

| Channel | Kapan Digunakan |
| - | - |
| **Offline** | Customer datang langsung ke toko |
| **Marketplace** | Order dari Shopee, Tokopedia, dll |
| **Website** | Order dari toko online |
| **WhatsApp** | Order via WhatsApp |
| **Reseller** | Order dari reseller |
| **B2B** | Order bisnis ke bisnis |
| **Grab** | Order dari GrabFood |
| **Social Media** | Order dari Instagram/Facebook |

### Customer Selection

| Opsi | Deskripsi |
| - | - |
| **Pilih Customer** | Dropdown untuk memilih customer yang sudah terdaftar |
| **Scan Member Card** | Scan barcode kartu member |
| **Input Nomor HP** | Cari customer berdasarkan nomor telepon |
| **Customer Baru** | Daftarkan customer baru langsung dari kasir |

### Quick Cash Buttons

Tombol cepat untuk nominal uang tunai yang umum:

| Tombol | Nominal |
| - | - |
| **50K** | Rp 50.000 |
| **100K** | Rp 100.000 |
| **150K** | Rp 150.000 |
| **200K** | Rp 200.000 |
| **Uang Pas** | Sesuai total |

### Split Payment

Aktifkan toggle split payment untuk kombinasi metode pembayaran:

```
Contoh Split Payment:
━━━━━━━━━━━━━━━━━━━━━━━━━
Total:            Rp 250.000
├─ Cash:          Rp 150.000  ✅
├─ QRIS:          Rp  50.000  ✅
└─ E-Wallet:      Rp  50.000  ⏳ (menunggu)
━━━━━━━━━━━━━━━━━━━━━━━━━
Sisa:             Rp   0
```

### Change Calculation

Kalkulasi kembalian otomatis untuk pembayaran cash:

```
Total:    Rp 150.000
Diterima: Rp 200.000
━━━━━━━━━━━━━━━━━━━━
Kembalian: Rp 50.000
```

## Transaction Features

### Hold Transaction

Tahan transaksi yang sedang diproses untuk diselesaikan nanti:

| Langkah | Aksi |
| - | - |
| 1 | Klik tombol **Hold** atau tekan `F4` |
| 2 | Transaksi disimpan dengan nomor hold |
| 3 | Keranjang dikosongkan untuk transaksi baru |
| 4 | Klik **Recall** untuk mengambil kembali transaksi yang ditahan |

### Void Transaction

Batalkan transaksi yang sudah diproses:

| Syarat | Deskripsi |
| - | - |
| **Waktu** | Maksimum 24 jam setelah transaksi |
| **Role** | Hanya Admin/Owner |
| **Alasan** | Wajib isi alasan void |
| **Efek** | Stok kembali, jurnal dibalik |

### Refund

Proses refund untuk transaksi yang sudah selesai:

| Langkah | Aksi |
| - | - |
| 1 | Cari transaksi yang akan di-refund |
| 2 | Pilih produk yang di-refund (partial/full) |
| 3 | Pilih metode refund (cash/transfer) |
| 4 | Konfirmasi refund |
| 5 | Stok otomatis kembali |
| 6 | Receipt refund di-generate |

## Receipt Printing

| Opsi | Deskripsi |
| - | - |
| **Auto-print** | Cetak otomatis setelah pembayaran selesai |
| **Bluetooth Thermal** | Support printer thermal Bluetooth (58mm & 80mm) |
| **PDF Download** | Simpan receipt sebagai PDF |
| **Email** | Kirim receipt ke email customer |
| **WhatsApp** | Kirim receipt via WhatsApp |

**Template Receipt:**

* Logo perusahaan
* Nama & alamat toko
* Tanggal & waktu
  \| No | Item | Qty | Harga | Subtotal |
  \|----|------|-----|-------|----------|
  \| | | | | |
  \| | | | **Total** | **Rp XXX** |
  \| | | | Metode | Cash/QRIS |
  \| | | | Kembalian | Rp XX |
  \| | **Terima kasih** | | | |

## Keyboard Shortcuts

| Shortcut | Fungsi | Context |
| - | - | - |
| `F1` | Focus ke search bar produk | Kasir |
| `F2` | Focus ke cart | Kasir |
| `F3` | Buka modal pembayaran | Kasir (cart tidak kosong) |
| `F4` | Hold transaction | Kasir |
| `F5` | Refresh halaman | Global |
| `Esc` | Tutup modal / batal | Global |
| `Enter` | Konfirmasi / bayar | Modal pembayaran |
| `Tab` | Navigasi antar field | Modal pembayaran |
| `Ctrl+P` | Print ulang receipt | Setelah transaksi |

## Offline Mode

| Kapabilitas | Status | Detail |
| - | - | - |
| Buat transaksi | ✅ | Tersimpan di IndexedDB |
| Scan barcode | ✅ | Lookup produk lokal |
| Hitung total | ✅ | Kalkulasi lokal |
| Cetak receipt | ✅ | Tidak perlu server |
| Sync saat online | ✅ | Auto-sync dengan antrian |
| Conflict resolution | ✅ | Server wins untuk stok |

## Tips

* **Selalu cek outlet yang dipilih** sebelum memproses transaksi — stok dan harga bisa berbeda antar outlet
* **Masukkan nomor member sebelum bayar** supaya poin dan diskon otomatis diterapkan
* **Gunakan shortcut keyboard** (`F1` search, `F3` bayar) untuk mempercepat transaksi saat antrian panjang
* **Manfaatkan hold transaction** jika customer belum siap bayar — kamu bisa lanjutkan transaksi lain dulu
* **Cek koneksi printer** di awal shift untuk menghindari masalah cetak receipt
* **Lakukan close shift** di akhir shift untuk rekonsiliasi uang kas dengan sistem

***

## Diagram Relasi Entitas

Berikut adalah diagram relasi entitas (ERD) yang menggambarkan seluruh entitas terkait modul Kasir (POS) beserta hubungan dan field-field kuncinya.

```mermaid theme={null}
erDiagram
    CompanyPOSTransaction {
        string company_id PK
        string transaction_number UK
        string invoice_number
        date_time transaction_date
        string payment_method
        string payment_status
        string order_status
        string status
        string source
        string sales_channel
        string cashier_id
        string customer_id FK
        number total
        number subtotal
        number discount_amount
        number tax_amount
        number payment_amount
        number change_amount
        number points_earned
        number points_used
    }

    CompanyPOSProduct {
        string company_id PK
        string name
        string sku UK
        string category
        string category_key
        string variant_key
        string barcode
        number price
        number cost
        number stock
        number min_stock
        string product_type
        boolean is_active
        boolean is_bundle
        number sold_count
        object channel_pricing
    }

    CompanyPOSCategory {
        string company_id PK
        string name
        string description
        string icon
        string color
        number order
    }

    CompanyPOSInventory {
        string company_id PK
        string product_id FK
        string product_name
        string type
        number quantity
        number stock_before
        number stock_after
        string reason
        string reference_id FK
        string performed_by
    }

    CompanyPOSSettings {
        string company_id PK
        object receipt_settings
    }

    CompanyReceiptSettings {
        string company_id PK
        boolean show_company_logo
        boolean show_customer_name
        boolean show_payment_details
        string custom_header_text
        string custom_footer_text
    }

    POSMember {
        string name
        string phone
        string email
        string membership_tier
        number points
        number total_spent
        number visit_count
        number discount_percentage
        boolean is_active
    }

    CustomerMembership {
        string company_id PK
        string level_name
        string level_key UK
        number discount_percentage
        number points_multiplier
        number min_purchase
        string scheme_type
        number points_threshold
        string reward_type
        number reward_value
    }

    Discount {
        string company_id PK
        string discount_name
        string discount_type
        number value
        string applies_to
        string coupon_code
        number usage_limit
        boolean is_active
    }

    Voucher {
        string code UK
        string name
        string discount_type
        number discount_value
        number min_purchase
        number usage_limit
        date_time valid_from
        date_time valid_until
        boolean is_active
    }

    PrinterSettings {
        string user_id FK
        string company_id
        string printer_name
        string device_id
        string paper_width
        string encoding
        boolean is_default
        boolean auto_cut
    }

    PaymentGatewaySettings {
        string gateway_name
        string api_key
        string private_key
        string mode
        string merchant_code
        boolean is_active
    }

    GLJournalEntry {
        string company_id PK
        date entry_date
        string reference_number
        string reference_type
        string reference_id FK
        number total_debit
        number total_credit
        boolean is_balanced
        string status
    }

    Expense {
        string company_id PK
        string requester_id
        string expense_code
        date expense_date
        string category
        number amount
        string status
        string payment_method
    }

    User {
        string email UK
        string full_name
        string role
        string subscription_plan
        string admin_type
        string admin_tier
        string active_company_id
    }

    WorkspaceMember {
        string workspace_id PK
        string user_id FK
        string role
        object permissions
    }

    CompanyPOSTransaction ||--o{ CompanyPOSProduct : "items (product_id)"
    CompanyPOSTransaction }o--|| POSMember : "customer_id"
    CompanyPOSTransaction }o--|| User : "cashier_id"
    CompanyPOSTransaction ||--o{ CompanyPOSInventory : "reference_id"
    CompanyPOSTransaction ||--o| GLJournalEntry : "reference_id"
    CompanyPOSProduct }o--|| CompanyPOSCategory : "category"
    CompanyPOSProduct ||--o{ CompanyPOSInventory : "product_id"
    CompanyPOSSettings ||--|| CompanyReceiptSettings : "company_id"
    POSMember }o--o| CustomerMembership : "membership_tier"
    CompanyPOSTransaction }o--o| Discount : "discount applied"
    CompanyPOSTransaction }o--o| Voucher : "voucher_code"
    PrinterSettings }o--o| User : "user_id"
    PaymentGatewaySettings ||--o{ CompanyPOSTransaction : "gateway"
    WorkspaceMember }o--|| User : "user_id"
```

***

## Tabel Schema Entitas

### CompanyPOSTransaction

Entitas utama untuk menyimpan seluruh transaksi POS kasir. Mendukung split payment, multi-channel, dan integrasi membership.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `location_id` | string | Tidak | ID lokasi gudang/toko |
| `location_name` | string | Tidak | Nama lokasi |
| `transaction_number` | string | Ya | Nomor transaksi unik (auto-generated) |
| `invoice_number` | string | Tidak | Nomor invoice |
| `transaction_date` | date-time | Tidak | Tanggal dan waktu transaksi |
| `items` | array\[object] | Ya | Daftar item yang dibeli (product\_id, product\_name, sku, quantity, price, discount, subtotal, unit, unit\_cost, total\_cost, note) |
| `subtotal` | number | Tidak | Subtotal sebelum diskon & pajak |
| `discount_amount` | number | Tidak | Nilai diskon dalam rupiah (default: 0) |
| `discount_percentage` | number | Tidak | Persentase diskon (default: 0) |
| `tax_amount` | number | Tidak | PPN atas transaksi. Mode inclusive: sudah termasuk di total; mode exclusive: tambahan di atas total (default: 0) |
| `total` | number | Ya | Total akhir transaksi |
| `total_amount` | number | Tidak | Total akhir (alias untuk total) |
| `payment_method` | enum | Tidak | Metode pembayaran: `cash`, `card`, `transfer`, `ewallet`, `qris`, `saldo`, `mayar`, `debt`, `manual_transfer`, `midtrans`, `tripay`, `stripe`, `paypal` |
| `payment_amount` | number | Tidak | Jumlah uang yang dibayar |
| `payments` | array\[object] | Tidak | Detail pembayaran multi-tender (split payment). Tiap entry: method, amount, change\_amount, account\_id, reference, verified\_by, verified\_at |
| `payment_status` | enum | Tidak | Status pembayaran: `pending`, `pending_verification`, `partially_paid`, `paid`, `failed`, `refunded`, `rejected` (default: `pending`) |
| `paid_amount` | number | Tidak | Nominal pembayaran aktual yang telah diverifikasi (default: 0) |
| `remaining_amount` | number | Tidak | Sisa kekurangan tagihan (default: 0) |
| `excess_amount` | number | Tidak | Kelebihan nominal transfer (default: 0) |
| `change_amount` | number | Tidak | Kembalian (default: 0) |
| `payment_reference` | string | Tidak | No. referensi bukti transfer/QRIS untuk pembayaran non-tunai |
| `payment_verified_by` | string | Tidak | Nama pengguna yang memverifikasi pembayaran non-tunai |
| `payment_verified_at` | date-time | Tidak | Waktu verifikasi pembayaran non-tunai |
| `customer_id` | string | Tidak | ID member jika ada |
| `customer_name` | string | Tidak | Nama customer |
| `customer_phone` | string | Tidak | Nomor telepon customer |
| `customer_address` | string | Tidak | Alamat customer |
| `points_earned` | number | Tidak | Poin loyalty yang didapat dari transaksi ini (default: 0) |
| `points_used` | number | Tidak | Poin loyalty yang dipakai untuk membayar (default: 0) |
| `cashier_id` | string | Tidak | ID karyawan kasir yang memproses |
| `cashier_name` | string | Tidak | Nama kasir yang memproses |
| `assigned_to_id` | string | Tidak | ID user yang ditugaskan memproses (online order) |
| `assigned_to_name` | string | Tidak | Nama user yang ditugaskan |
| `notes` | string | Tidak | Catatan transaksi |
| `source` | enum | Tidak | Sumber transaksi: `pos`, `online_catalog`, `website`, `marketplace`, `landing_page`, `whatsapp`, `reseller`, `b2b`, `grab`, `social_media` (default: `pos`) |
| `order_status` | enum | Tidak | Status pesanan: `pending`, `processing`, `shipped`, `delivered`, `completed`, `cancelled`, `rejected` (default: `pending`) |
| `status` | enum | Tidak | Status transaksi: `pending`, `processing`, `shipped`, `delivered`, `completed`, `failed`, `refunded`, `cancelled` (default: `completed`) |
| `tracking_number` | string | Tidak | Nomor resi pengiriman |
| `shipping_address` | string | Tidak | Alamat pengiriman |
| `sales_channel` | enum | Tidak | Channel penjualan ternormalisasi: `offline_pos`, `offline`, `website`, `online_catalog`, `marketplace`, `landing_page`, `whatsapp`, `reseller`, `b2b`, `grab`, `social_media` (default: `offline_pos`) |
| `metadata` | object | Tidak | Data tambahan (online order info, dll) |
| `verified_at` | date-time | Tidak | Waktu verifikasi transaksi |
| `verified_by` | string | Tidak | User yang memverifikasi |
| `processed_at` | date-time | Tidak | Waktu transaksi diproses |
| `shipped_at` | date-time | Tidak | Waktu pengiriman |
| `delivered_at` | date-time | Tidak | Waktu penerimaan |
| `cancelled_at` | date-time | Tidak | Waktu pembatalan |

### CompanyPOSProduct

Entitas produk yang dijual melalui kasir POS. Mendukung variant, bundle, channel pricing, dan data kualitas (HACCP).

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `name` | string | Ya | Nama produk |
| `sku` | string | Tidak | SKU/Barcode produk |
| `category` | string | Tidak | Kategori produk (label bebas) |
| `category_key` | string | Tidak | Kategori canonical: kecil, besar, pouch, bundle, snack |
| `category_name` | string | Tidak | Label kategori canonical |
| `variant_key` | string | Tidak | Varian canonical: original, extra\_spicy, bundle |
| `variant_name` | string | Tidak | Label varian canonical |
| `variant_label` | string | Tidak | Gabungan kategori dan varian untuk POS/inventory/manufacturing |
| `size_grams` | number | Tidak | Ukuran canonical produk dalam gram |
| `base_product_key` | string | Tidak | Kunci produk utama sebelum kategori/varian |
| `description` | string | Tidak | Deskripsi produk |
| `price` | number | Ya | Harga jual |
| `cost` | number | Tidak | Harga modal/beli |
| `stock` | number | Tidak | Stok saat ini (default: 0) |
| `min_stock` | number | Tidak | Minimum stok untuk alert (default: 5) |
| `image_url` | string | Tidak | URL gambar utama produk |
| `gallery` | array\[string] | Tidak | Galeri foto produk (maks 20 foto) |
| `variants` | array\[object] | Tidak | Varian produk (name, sku, price, stock) |
| `is_active` | boolean | Tidak | Status aktif produk (default: true) |
| `deactivated_at` | date-time | Tidak | Waktu SKU dinonaktifkan tanpa menghapus histori |
| `deactivated_by` | string | Tidak | Akun yang menonaktifkan SKU |
| `tax_rate` | number | Tidak | Persentase pajak 0-100 (default: 0) |
| `supplier` | string | Tidak | Nama supplier |
| `unit` | string | Tidak | Satuan produk (default: `pcs`) |
| `variant` | string | Tidak | Varian produk (Pedas, Manis, Gurih, dll) |
| `barcode` | string | Tidak | Barcode / QR Code produk (EAN-13) |
| `product_type` | enum | Tidak | Tipe produk: `finished_good`, `raw_material`, `semi_finished`, `packaging_material`, `bundle` (default: `finished_good`) |
| `packaging_type` | enum | Tidak | Jenis kemasan: `jar_glass`, `pouch_zipper`, `pouch_sealed`, `toples_plastic`, `bottle_plastic`, `bulk`, `other` |
| `net_weight_grams` | number | Tidak | Berat bersih isi produk dalam gram |
| `gross_weight_grams` | number | Tidak | Berat kotor termasuk kemasan dalam gram |
| `storage_condition` | enum | Tidak | Kondisi penyimpanan: `room_temperature`, `chilled`, `frozen` (default: `room_temperature`) |
| `is_bundle` | boolean | Tidak | Menandakan paket bundle virtual (default: false) |
| `bundle_components` | array\[object] | Tidak | Komponen SKU penyusun bundle (sku, product\_id, name, quantity, unit) |
| `channel_pricing` | object | Tidak | Harga khusus per channel (offline\_pos, marketplace, website, whatsapp, reseller, b2b, grab, social\_media) |
| `is_locked` | boolean | Tidak | Data terkunci karena sudah ada transaksi (default: false) |
| `locked_at` | date-time | Tidak | Waktu penguncian data |
| `locked_by` | string | Tidak | Email user yang mengunci data |
| `lock_reason` | string | Tidak | Alasan penguncian atau reason code perubahan harga |
| `sold_count` | number | Tidak | Total jumlah produk terjual (default: 0) |

### CompanyPOSInventory

Log pergerakan stok produk. Setiap transaksi kasir otomatis mencatat entri `out` pada entitas ini.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `product_id` | string | Ya | ID produk yang berubah stoknya |
| `product_name` | string | Tidak | Nama produk (denormalized) |
| `type` | enum | Ya | Tipe pergerakan stok: `in`, `out`, `adjustment` |
| `quantity` | number | Ya | Jumlah perubahan stok |
| `stock_before` | number | Tidak | Stok sebelum perubahan |
| `stock_after` | number | Tidak | Stok setelah perubahan |
| `reason` | string | Tidak | Alasan (pembelian, penjualan, rusak, dll) |
| `reference_id` | string | Tidak | ID transaksi terkait |
| `notes` | string | Tidak | Catatan tambahan |
| `performed_by` | string | Tidak | User yang melakukan perubahan |

### CompanyPOSCategory

Kategori produk dalam lingkup perusahaan untuk mengelompokkan produk di product grid kasir.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `name` | string | Ya | Nama kategori |
| `description` | string | Tidak | Penjelasan jenis produk dalam kategori ini (maks 1000 karakter) |
| `icon` | string | Tidak | Icon emoji |
| `color` | string | Tidak | Warna hex kategori (default: `#3b82f6`) |
| `order` | number | Tidak | Urutan pengurutan (default: 0) |

### CompanyPOSSettings

Pengaturan global POS per perusahaan, termasuk konfigurasi receipt.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `description` | string | Tidak | Catatan konfigurasi sistem kasir (maks 1000 karakter) |
| `receipt_settings` | object | Tidak | Objek konfigurasi receipt (lihat sub-tabel di bawah) |

**Sub-field `receipt_settings`:**

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `logo_url` | string | Tidak | URL logo perusahaan untuk struk |
| `business_name` | string | Tidak | Nama bisnis di struk |
| `business_address` | string | Tidak | Alamat lengkap |
| `business_phone` | string | Tidak | Nomor telepon |
| `business_email` | string | Tidak | Email bisnis |
| `tax_id` | string | Tidak | NPWP/Tax ID |
| `receipt_width` | enum | Tidak | Ukuran kertas struk: `58mm`, `80mm` (default: `80mm`) |
| `show_logo` | boolean | Tidak | Tampilkan logo (default: true) |
| `show_tax_id` | boolean | Tidak | Tampilkan NPWP (default: false) |
| `show_cashier_name` | boolean | Tidak | Tampilkan nama kasir (default: true) |
| `show_customer_info` | boolean | Tidak | Tampilkan info customer (default: true) |
| `show_item_image` | boolean | Tidak | Tampilkan gambar item (default: false) |
| `header_text` | string | Tidak | Header struk (default: `STRUK PEMBELIAN`) |
| `footer_message` | string | Tidak | Pesan footer struk |
| `print_auto_after_transaction` | boolean | Tidak | Auto-print setelah transaksi (default: true) |
| `font_size` | enum | Tidak | Ukuran font: `small`, `medium`, `large` (default: `medium`) |

### CompanyReceiptSettings

Pengaturan tampilan struk pembelian yang lebih detail, terpisah dari CompanyPOSSettings.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `description` | string | Tidak | Catatan konfigurasi struk (maks 1000 karakter) |
| `show_company_logo` | boolean | Tidak | Tampilkan logo perusahaan (default: true) |
| `show_company_address` | boolean | Tidak | Tampilkan alamat perusahaan (default: true) |
| `show_customer_name` | boolean | Tidak | Tampilkan nama customer (default: true) |
| `show_customer_points` | boolean | Tidak | Tampilkan total poin customer (default: true) |
| `show_points_earned` | boolean | Tidak | Tampilkan poin yang didapat dari transaksi ini (default: true) |
| `show_payment_details` | boolean | Tidak | Tampilkan detail pembayaran (default: true) |
| `show_change` | boolean | Tidak | Tampilkan kembalian (default: true) |
| `custom_header_text` | string | Tidak | Teks custom di header struk |
| `custom_footer_text` | string | Tidak | Teks custom di footer struk (default: `Terima kasih sudah berbelanja!`) |
| `show_promo_message` | boolean | Tidak | Tampilkan pesan promo (default: false) |
| `promo_message` | string | Tidak | Pesan promo/marketing |

### POSMember

Data member/customer yang terdaftar dalam program loyalty POS.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `name` | string | Ya | Nama member |
| `phone` | string | Ya | Nomor telepon |
| `email` | string | Tidak | Email |
| `address` | string | Tidak | Alamat |
| `birth_date` | date | Tidak | Tanggal lahir |
| `membership_tier` | enum | Tidak | Level membership: `regular`, `silver`, `gold`, `platinum` (default: `regular`) |
| `points` | number | Tidak | Total poin saat ini (default: 0) |
| `lifetime_points` | number | Tidak | Total poin sepanjang waktu (default: 0) |
| `stamps` | number | Tidak | Total stamp saat ini (default: 0) |
| `has_negative_points` | boolean | Tidak | Flag saldo negatif akibat pembatalan/void (default: false) |
| `loyalty_deficit_points` | number | Tidak | Defisit poin loyalty setelah retur/void (default: 0) |
| `loyalty_review_notes` | string | Tidak | Catatan review penanganan loyalty deficit |
| `last_tier_upgrade_date` | date-time | Tidak | Waktu perubahan level membership terakhir |
| `last_tier_upgrade_reason` | string | Tidak | Alasan perubahan level membership |
| `last_tier_upgrade_by` | string | Tidak | Email operator yang mengubah level |
| `total_spent` | number | Tidak | Total belanja (default: 0) |
| `visit_count` | number | Tidak | Jumlah kunjungan/transaksi (default: 0) |
| `last_visit` | date-time | Tidak | Waktu kunjungan terakhir |
| `discount_percentage` | number | Tidak | Diskon khusus member (default: 0) |
| `notes` | string | Tidak | Catatan |
| `is_active` | boolean | Tidak | Status aktif member (default: true) |

### CustomerMembership

Definisi tier/level membership loyalty per perusahaan. Menentukan syarat, benefit, dan skema loyalty.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `level_name` | string | Ya | Nama level (Silver, Gold, Platinum, dll) |
| `level_key` | string | Ya | Key unik untuk level (silver, gold, platinum) |
| `icon` | string | Tidak | Icon emoji untuk level |
| `color` | string | Tidak | Warna untuk level |
| `description` | string | Tidak | Penjelasan keuntungan dan syarat keanggotaan (maks 1000 karakter) |
| `discount_percentage` | number | Tidak | Diskon % untuk member level ini 0-100 (default: 0) |
| `points_multiplier` | number | Tidak | Multiplier poin (1 = normal, 2 = double) (default: 1) |
| `min_purchase` | number | Tidak | Minimum pembelian untuk mendapat level ini (default: 0) |
| `benefits` | array\[string] | Tidak | List benefit yang didapat |
| `priority_support` | boolean | Tidak | Akses priority support via WhatsApp (default: false) |
| `free_delivery` | boolean | Tidak | Gratis ongkir (default: false) |
| `birthday_bonus` | number | Tidak | Bonus poin di hari ulang tahun (default: 0) |
| `is_active` | boolean | Tidak | Status aktif (default: true) |
| `order` | number | Tidak | Urutan level (default: 0) |
| `scheme_type` | enum | Tidak | Skema loyalty: `points`, `stamp`, `spending`, `visits`, `hybrid` (default: `points`) |
| `points_threshold` | number | Tidak | Ambang batas belanja per perolehan poin (default: 10000) |
| `points_per_threshold` | number | Tidak | Jumlah poin per threshold (default: 1) |
| `stamps_required` | number | Tidak | Jumlah stamp untuk reward/naik level (default: 10) |
| `stamp_per_transaction` | number | Tidak | Jumlah stamp per transaksi eligible (default: 1) |
| `min_redemption_points` | number | Tidak | Minimal poin untuk tukar reward (default: 0) |
| `reward_type` | enum | Tidak | Jenis reward: `none`, `discount_percentage`, `discount_amount`, `free_product` (default: `none`) |
| `reward_value` | number | Tidak | Nilai reward (persen/nominal) (default: 0) |
| `reward_product_id` | string | Tidak | ID produk reward gratis jika reward\_type = `free_product` |
| `reward_product_name` | string | Tidak | Nama produk reward gratis |
| `expiry_days` | number | Tidak | Masa kedaluwarsa poin dalam hari (opsional) |

### Discount

Konfigurasi diskon yang dapat diterapkan pada transaksi kasir.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `discount_name` | string | Ya | Nama diskon |
| `discount_type` | enum | Ya | Tipe diskon: `percentage`, `fixed_amount`, `buy_x_get_y`, `coupon_code` (default: `percentage`) |
| `value` | number | Ya | Nilai diskon (% atau nominal) |
| `buy_quantity` | number | Tidak | Untuk tipe buy\_x\_get\_y: jumlah beli |
| `get_quantity` | number | Tidak | Untuk tipe buy\_x\_get\_y: jumlah gratis |
| `min_purchase_amount` | number | Tidak | Minimum pembelian (default: 0) |
| `max_discount_amount` | number | Tidak | Batas maksimal nilai diskon |
| `applies_to` | enum | Tidak | Berlaku untuk: `all_products`, `specific_products`, `specific_categories` (default: `all_products`) |
| `product_ids` | array\[string] | Tidak | Daftar ID produk target |
| `category_names` | array\[string] | Tidak | Daftar nama kategori target |
| `coupon_code` | string | Tidak | Kode kupon unik |
| `usage_limit` | number | Tidak | Batas penggunaan (0 = unlimited) |
| `used_count` | number | Tidak | Jumlah sudah digunakan (default: 0) |
| `start_date` | date | Tidak | Tanggal mulai berlaku |
| `end_date` | date | Tidak | Tanggal berakhir |
| `is_active` | boolean | Tidak | Status aktif (default: true) |
| `description` | string | Tidak | Deskripsi |

### Voucher

Kode voucher yang dapat digunakan customer untuk mendapatkan potongan harga.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `code` | string | Ya | Kode voucher unik |
| `name` | string | Ya | Nama voucher |
| `description` | string | Tidak | Penjelasan syarat dan ketentuan voucher (maks 1000 karakter) |
| `discount_type` | enum | Ya | Jenis potongan: `percentage`, `fixed_amount` |
| `discount_value` | number | Ya | Nilai potongan (persentase 0-100 atau jumlah dalam rupiah) |
| `min_purchase` | number | Tidak | Minimum pembelian (default: 0) |
| `max_discount` | number | Tidak | Maksimum potongan (untuk persentase) |
| `usage_limit` | number | Tidak | Batas penggunaan (null = unlimited) |
| `usage_count` | number | Tidak | Jumlah sudah digunakan (default: 0) |
| `valid_from` | date-time | Ya | Tanggal mulai aktif |
| `valid_until` | date-time | Ya | Tanggal berakhir |
| `applicable_plans` | array\[enum] | Tidak | Paket yang bisa menggunakan: `pro`, `business`, `advanced`, `enterprise` |
| `is_active` | boolean | Tidak | Status aktif (default: true) |
| `created_by` | string | Tidak | Admin yang membuat voucher |

### PrinterSettings

Konfigurasi printer thermal Bluetooth yang digunakan kasir untuk mencetak receipt.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Tidak | ID perusahaan (null untuk personal) |
| `user_id` | string | Ya | ID user pemilik |
| `printer_name` | string | Tidak | Nama printer yang tersimpan |
| `description` | string | Tidak | Catatan konfigurasi printer (maks 1000 karakter) |
| `device_id` | string | Tidak | Bluetooth device ID |
| `device_name` | string | Tidak | Nama device dari Bluetooth |
| `paper_width` | enum | Tidak | Lebar kertas thermal: `58mm`, `80mm` (default: `80mm`) |
| `encoding` | enum | Tidak | Character encoding: `UTF-8`, `GB18030`, `Big5`, `Shift_JIS`, `EUC-KR` (default: `UTF-8`) |
| `auto_cut` | boolean | Tidak | Auto cut paper setelah print (default: true) |
| `beep_on_print` | boolean | Tidak | Beep sound saat print (default: false) |
| `auto_reconnect` | boolean | Tidak | Auto reconnect jika disconnect (default: true) |
| `print_logo` | boolean | Tidak | Print logo di struk (default: true) |
| `logo_url` | string | Tidak | URL logo untuk di-print |
| `header_text` | string | Tidak | Header struk (default: `STRUK PEMBELIAN`) |
| `footer_text` | string | Tidak | Footer struk (default: `Terima kasih atas kunjungan Anda`) |
| `font_size` | enum | Tidak | Ukuran font: `small`, `normal`, `large` (default: `normal`) |
| `last_connected` | date-time | Tidak | Terakhir kali terhubung |
| `connection_count` | number | Tidak | Jumlah koneksi berhasil (default: 0) |
| `is_default` | boolean | Tidak | Printer default (default: false) |

### PaymentGatewaySettings

Konfigurasi payment gateway (Tripay) untuk pembayaran non-tunai.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `gateway_name` | enum | Ya | Nama payment gateway: `tripay` (default: `tripay`) |
| `description` | string | Tidak | Catatan konfigurasi (maks 1000 karakter) |
| `api_key` | string | Tidak | API Key dari Tripay |
| `private_key` | string | Tidak | Private Key dari Tripay (hanya admin) |
| `is_active` | boolean | Tidak | Status aktif (default: false) |
| `mode` | enum | Tidak | Mode environment: `sandbox`, `production` (default: `sandbox`) |
| `merchant_code` | string | Tidak | Kode merchant dari Tripay |
| `callback_url` | string | Tidak | URL callback untuk notifikasi |
| `whitelisted_ips` | array\[string] | Tidak | Daftar IP yang di-whitelist |
| `available_payment_methods` | array\[string] | Tidak | Metode pembayaran yang tersedia (default: \[]) |
| `config_notes` | string | Tidak | Catatan konfigurasi dari admin |
| `last_verified` | date-time | Tidak | Terakhir kali verifikasi koneksi berhasil |

### GLJournalEntry

Entitas jurnal akuntansi yang di-generate otomatis dari transaksi kasir untuk pencatatan double-entry bookkeeping.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `entry_date` | date | Ya | Tanggal entri jurnal |
| `reference_number` | string | Tidak | Nomor referensi (TRX-001, INV-001, dll) |
| `reference_type` | enum | Tidak | Tipe referensi: `manual`, `invoice`, `purchase_order`, `transfer`, `expense` |
| `reference_id` | string | Tidak | ID dokumen sumber (misalnya ID CompanyPOSTransaction) |
| `description` | string | Ya | Deskripsi jurnal |
| `line_items` | array\[object] | Ya | Line items debit/credit (account\_id, account\_code, debit, credit, description) |
| `total_debit` | number | Tidak | Total debit (default: 0) |
| `total_credit` | number | Tidak | Total kredit (default: 0) |
| `is_balanced` | boolean | Tidak | Apakah debit = credit (default: false) |
| `status` | enum | Tidak | Status jurnal: `draft`, `posted`, `reversed` (default: `draft`) |
| `posted_date` | date | Tidak | Tanggal di-post |
| `posted_by` | string | Tidak | User yang mem-post |
| `notes` | string | Tidak | Catatan |

***

## State Machine — Siklus Hidup Transaksi Kasir

Berikut diagram state yang menggambarkan siklus hidup transaksi dari pembuatan hingga penyelesaian, termasuk flow void dan refund.

```mermaid theme={null}
stateDiagram-v2
    [*] --> Draft : Kasir mulai transaksi baru

    state "Draft" as Draft
    state "Pending" as Pending
    state "Processing" as Processing
    state "Paid" as Paid
    state "Pending Verification" as PendingVerification
    state "Completed" as Completed
    state "Partially Paid" as PartiallyPaid
    state "Failed" as Failed
    state "Refunded" as Refunded
    state "Cancelled" as Cancelled
    state "Rejected" as Rejected

    Draft --> Pending : Tambah item ke cart
    Draft --> [*] : Batal (cart kosong)

    Pending --> Pending : Tambah/kurang item
    Pending --> Pending : Hold (F4)
    Pending --> Pending : Recall hold
    Pending --> Paid : Pembayaran tunai penuh
    Pending --> PendingVerification : Pembayaran non-tunai (butuh verifikasi)
    Pending --> PartiallyPaid : Split payment belum lunas
    Pending --> Failed : Pembayaran gagal
    Pending --> Cancelled : Customer batal

    PendingVerification --> Paid : Verifikasi bukti transfer OK
    PendingVerification --> Rejected : Verifikasi ditolak
    PendingVerification --> Failed : Timeout verifikasi

    PartiallyPaid --> Paid : Pelunasan selesai
    PartiallyPaid --> Cancelled : Customer abandon

    Paid --> Completed : Stok berkurang, jurnal tercatat, receipt dicetak

    Completed --> Refunded : Retur parsial/full
    Completed --> Cancelled : Void (maks 24 jam, hanya Admin/Owner)

    Refunded --> [*]
    Cancelled --> [*]
    Rejected --> [*]
    Failed --> [*]
    Completed --> [*]
```

**Deskripsi transisi utama:**

| Transisi | Trigger | Efek |
| - | - | - |
| Draft → Pending | Kasir menambahkan item pertama ke cart | Transaksi mulai dicatat |
| Pending → Paid | Pembayaran penuh (cash/ewallet/QRIS otomatis) | Total terbayar |
| Pending → PendingVerification | Upload bukti transfer/manual transfer | Menunggu verifikasi admin |
| PendingVerification → Paid | Admin verifikasi bukti transfer | Stok & jurnal diproses |
| Paid → Completed | Auto-transition setelah pembayaran confirmed | Stok berkurang, jurnal tercatat, receipt dicetak |
| Completed → Refunded | Kasir/Admin proses retur | Stok kembali, jurnal dibalik, receipt refund |
| Completed → Cancelled | Void oleh Admin/Owner (maks 24 jam) | Stok kembali, jurnal reverse, poin ditarik |

***

## Sequence Diagrams

### 1. Flow Checkout Kasir (Scan → Bayar → Receipt)

```mermaid theme={null}
sequenceDiagram
    participant Kasir
    participant UI as Frontend Kasir
    participant IndexedDB as IndexedDB (Offline Cache)
    participant API as Backend API
    participant Inv as CompanyPOSInventory
    participant Jurnal as GLJournalEntry
    participant Member as POSMember
    participant Printer as Printer Thermal

    Kasir->>UI: Scan barcode / klik produk
    UI->>UI: Lookup produk di local cache
    UI->>UI: Tambahkan item ke cart
    UI->>UI: Hitung subtotal real-time

    alt Member terdaftar
        Kasir->>UI: Input nomor HP / scan member card
        UI->>API: GET /posmember?phone=xxx
        API-->>UI: Data member (tier, points, discount)
        UI->>UI: Terapkan diskon member & multiplier poin
    end

    Kasir->>UI: Klik tombol BAYAR (F3)
    UI->>UI: Buka modal pembayaran

    alt Pembayaran Cash
        Kasir->>UI: Input nominal uang diterima
        UI->>UI: Hitung kembalian otomatis
    else Pembayaran Non-Tunai
        Kasir->>UI: Pilih metode (QRIS/Transfer/E-Wallet)
        UI->>API: Request payment gateway
        API-->>UI: QR code / payment URL
        Kasir->>UI: Konfirmasi pembayaran diterima
    end

    alt Split Payment
        Kasir->>UI: Aktifkan toggle split payment
        Kasir->>UI: Tambah tender 1 (Cash: 150.000)
        Kasir->>UI: Tambah tender 2 (QRIS: 100.000)
        UI->>UI: Validasi total tender = total tagihan
    end

    Kasir->>UI: Konfirmasi bayar (Enter)
    UI->>API: POST /companyposcashier (finalize)

    API->>API: Generate transaction_number
    API->>Inv: Catat pergerakan stok (type: out)
    API->>Jurnal: Buat jurnal otomatis (Debit: Kas, Credit: Penjualan)
    
    alt Member terdaftar
        API->>Member: Tambah poin (points_earned)
        API->>Member: Update total_spent & visit_count
        API->>Member: Cek kenaikan tier
    end

    API-->>UI: Response: transaksi berhasil
    UI->>Printer: Kirim data receipt (Bluetooth/WebUSB)
    Printer-->>Kasir: Cetak struk
    UI->>UI: Reset cart untuk transaksi baru
```

### 2. Flow Void / Retur Transaksi

```mermaid theme={null}
sequenceDiagram
    participant Kasir
    participant UI as Frontend Kasir
    participant API as Backend API
    participant Inv as CompanyPOSInventory
    participant Jurnal as GLJournalEntry
    participant Member as POSMember

    Kasir->>UI: Cari transaksi yang akan di-void/retur
    UI->>API: GET /companyposcashier?transaction_number=xxx
    API-->>UI: Data transaksi lengkap

    alt Void Transaction
        Kasir->>UI: Klik Void
        UI->>UI: Modal alasan void (wajib isi)
        Kasir->>UI: Input alasan void
        UI->>API: PUT /companyposcashier/:id (status: cancelled)

        API->>API: Validasi: maks 24 jam & role Admin/Owner
        alt Validasi gagal
            API-->>UI: Error: tidak memenuhi syarat void
        else Validasi OK
            API->>Inv: Kembalikan stok (type: in, reason: void)
            API->>Jurnal: Reverse jurnal (swap debit/credit)
            API->>Member: Tarik poin (loyalty_deficit_points)
            API->>Member: Set has_negative_points = true jika perlu
            API-->>UI: Response: void berhasil
        end

    else Refund (Partial/Full)
        Kasir->>UI: Pilih produk yang di-refund
        Kasir->>UI: Pilih metode refund (cash/transfer)
        Kasir->>UI: Konfirmasi refund
        UI->>API: PUT /companyposcashier/:id (status: refunded)

        API->>Inv: Kembalikan stok produk yang di-refund
        API->>Jurnal: Buat jurnal retur (Debit: Retur, Credit: Kas)
        API->>Member: Sesuaikan poin (proportional)
        API-->>UI: Response: refund berhasil
        UI->>UI: Generate receipt refund
    end

    UI->>UI: Tampilkan notifikasi sukses
```

### 3. Flow Offline Mode Sync

```mermaid theme={null}
sequenceDiagram
    participant Kasir
    participant UI as Frontend Kasir
    participant IDB as IndexedDB
    participant Sync as Sync Engine
    participant API as Backend API

    Kasir->>UI: Scan barcode produk
    UI->>IDB: Lookup produk di local cache
    IDB-->>UI: Data produk lokal (nama, harga, stok)
    UI->>UI: Tambahkan ke cart

    Kasir->>UI: Proses pembayaran
    UI->>UI: Hitung total secara lokal
    UI->>IDB: Simpan transaksi ke queue (status: pending_sync)
    IDB-->>UI: ACK: tersimpan di antrian

    Note over UI: Indikator: "Offline - 1 transaksi menunggu sync"

    Kasir->>UI: Cetak receipt
    UI->>UI: Generate receipt dari data lokal

    Note over UI,Kasir: === Koneksi pulih ===

    UI->>Sync: Detect koneksi online
    Sync->>IDB: Baca antrian transaksi pending
    IDB-->>Sync: List transaksi pending_sync

    loop Untuk setiap transaksi dalam antrian
        Sync->>API: POST /companyposcashier (sync transaction)
        
        alt Server accepts
            API->>API: Validasi stok server vs lokal
            API->>API: Generate transaction_number server
            API-->>Sync: Response: OK (server_id, transaction_number)
            Sync->>IDB: Update status: synced + server_id
        else Conflict (stok tidak cukup)
            API-->>Sync: Response: CONFLICT
            Sync->>Sync: Server wins: update stok lokal
            Sync->>IDB: Update status: synced + flag conflict
        else Network error
            API-->>Sync: Error: timeout/unreachable
            Sync->>IDB: Tetap pending, retry nanti
        end
    end

    Sync->>UI: Update status sync indicator
    UI->>Kasir: Notifikasi: "Semua transaksi tersinkronisasi"
```

***

## Tabel Referensi Enum

### payment\_method (CompanyPOSTransaction)

| Nilai | Deskripsi |
| - | - |
| `cash` | Pembayaran tunai |
| `card` | Kartu debit/kredit |
| `transfer` | Transfer bank manual |
| `ewallet` | Dompet digital (GoPay, OVO, Dana, dll) |
| `qris` | QRIS (QR code payment) |
| `saldo` | Bayar dari saldo deposit |
| `mayar` | Mayar payment gateway |
| `debt` | Hutang/piutang (tempo) |
| `manual_transfer` | Transfer manual dengan verifikasi |
| `midtrans` | Midtrans payment gateway |
| `tripay` | Tripay payment gateway |
| `stripe` | Stripe payment gateway |
| `paypal` | PayPal payment gateway |

### payment\_status (CompanyPOSTransaction)

| Nilai | Deskripsi |
| - | - |
| `pending` | Menunggu pembayaran |
| `pending_verification` | Menunggu verifikasi bukti transfer oleh admin |
| `partially_paid` | Baru terbayar sebagian (split payment) |
| `paid` | Sudah dibayar penuh |
| `failed` | Pembayaran gagal |
| `refunded` | Sudah direfund |
| `rejected` | Pembayaran ditolak (verifikasi gagal) |

### order\_status (CompanyPOSTransaction)

| Nilai | Deskripsi |
| - | - |
| `pending` | Pesanan baru masuk, belum diproses |
| `processing` | Sedang diproses/disiapkan |
| `shipped` | Sudah dikirim |
| `delivered` | Sudah diterima customer |
| `completed` | Selesai |
| `cancelled` | Dibatalkan |
| `rejected` | Ditolak |

### status (CompanyPOSTransaction)

| Nilai | Deskripsi |
| - | - |
| `pending` | Menunggu |
| `processing` | Diproses |
| `shipped` | Dikirim |
| `delivered` | Terkirim |
| `completed` | Selesai |
| `failed` | Gagal |
| `refunded` | Direfund |
| `cancelled` | Dibatalkan |

### source / sales\_channel (CompanyPOSTransaction)

| Nilai | Deskripsi |
| - | - |
| `pos` / `offline_pos` | Transaksi dari kasir offline |
| `online_catalog` | Katalog online |
| `website` | Website toko online |
| `marketplace` | Marketplace (Shopee, Tokopedia, dll) |
| `landing_page` | Landing page |
| `whatsapp` | Order via WhatsApp |
| `reseller` | Order dari reseller |
| `b2b` | Order B2B |
| `grab` | Order dari GrabFood |
| `social_media` | Order dari Instagram/Facebook |
| `offline` | Offline (legacy) |

### inventory\_type (CompanyPOSInventory)

| Nilai | Deskripsi |
| - | - |
| `in` | Stok masuk (pembelian, retur, void) |
| `out` | Stok keluar (penjualan) |
| `adjustment` | Penyesuaian stok (stock opname, rusak, dll) |

### membership\_tier (POSMember)

| Nilai | Deskripsi |
| - | - |
| `regular` | Member biasa (tanpa benefit khusus) |
| `silver` | Member silver |
| `gold` | Member gold |
| `platinum` | Member platinum (tier tertinggi) |

### scheme\_type (CustomerMembership)

| Nilai | Deskripsi |
| - | - |
| `points` | Loyalty berbasis poin |
| `stamp` | Loyalty berbasis stamp/cap |
| `spending` | Loyalty berbasis total belanja |
| `visits` | Loyalty berbasis jumlah kunjungan |
| `hybrid` | Kombinasi beberapa skema |

### reward\_type (CustomerMembership)

| Nilai | Deskripsi |
| - | - |
| `none` | Tidak ada reward |
| `discount_percentage` | Reward berupa diskon persentase |
| `discount_amount` | Reward berupa diskon nominal |
| `free_product` | Reward berupa produk gratis |

### discount\_type (Discount)

| Nilai | Deskripsi |
| - | - |
| `percentage` | Diskon persentase |
| `fixed_amount` | Diskon nominal tetap |
| `buy_x_get_y` | Beli X gratis Y |
| `coupon_code` | Kupon dengan kode khusus |

### applies\_to (Discount)

| Nilai | Deskripsi |
| - | - |
| `all_products` | Berlaku untuk semua produk |
| `specific_products` | Hanya produk tertentu |
| `specific_categories` | Hanya kategori tertentu |

### product\_type (CompanyPOSProduct)

| Nilai | Deskripsi |
| - | - |
| `finished_good` | Produk jadi siap jual |
| `raw_material` | Bahan baku |
| `semi_finished` | Produk setengah jadi |
| `packaging_material` | Bahan kemasan |
| `bundle` | Paket bundle virtual |

### packaging\_type (CompanyPOSProduct)

| Nilai | Deskripsi |
| - | - |
| `jar_glass` | Toples kaca |
| `pouch_zipper` | Pouch zipper |
| `pouch_sealed` | Pouch sealed |
| `toples_plastic` | Toples plastik |
| `bottle_plastic` | Botol plastik |
| `bulk` | Curah |
| `other` | Lainnya |

### storage\_condition (CompanyPOSProduct)

| Nilai | Deskripsi |
| - | - |
| `room_temperature` | Suhu ruang |
| `chilled` | Dingin |
| `frozen` | Beku |

### receipt\_width / paper\_width (CompanyPOSSettings, PrinterSettings)

| Nilai | Deskripsi |
| - | - |
| `58mm` | Kertas thermal kecil (58mm) |
| `80mm` | Kertas thermal standar (80mm) |

### font\_size (CompanyPOSSettings, PrinterSettings)

| Nilai | Deskripsi |
| - | - |
| `small` | Font kecil |
| `medium` / `normal` | Font sedang |
| `large` | Font besar |

### encoding (PrinterSettings)

| Nilai | Deskripsi |
| - | - |
| `UTF-8` | Unicode standar (default) |
| `GB18030` | Chinese Simplified |
| `Big5` | Chinese Traditional |
| `Shift_JIS` | Japanese |
| `EUC-KR` | Korean |

### gl\_reference\_type (GLJournalEntry)

| Nilai | Deskripsi |
| - | - |
| `manual` | Entri manual |
| `invoice` | Dari invoice |
| `purchase_order` | Dari purchase order |
| `transfer` | Dari transfer |
| `expense` | Dari pengeluaran |

### gl\_status (GLJournalEntry)

| Nilai | Deskripsi |
| - | - |
| `draft` | Belum di-post |
| `posted` | Sudah di-post ke buku besar |
| `reversed` | Sudah dibalik (reversal) |

### expense\_category (Expense)

| Nilai | Deskripsi |
| - | - |
| `salary` | Gaji karyawan |
| `raw_material` | Bahan baku |
| `operational` | Operasional |
| `production_cost` | Biaya produksi |
| `travel` | Perjalanan dinas |
| `meals` | Konsumsi |
| `accommodation` | Akomodasi |
| `equipment` | Peralatan |
| `office_supplies` | Perlengkapan kantor |
| `training` | Pelatihan |
| `other` | Lainnya |

### expense\_status (Expense)

| Nilai | Deskripsi |
| - | - |
| `draft` | Draft |
| `submitted` | Sudah diajukan |
| `approved` | Disetujui |
| `rejected` | Ditolak |
| `paid` | Sudah dibayar |

### user\_role (User)

| Nilai | Deskripsi |
| - | - |
| `admin` | Administrator (full access) |
| `user` | User biasa |

### admin\_type (User)

| Nilai | Deskripsi |
| - | - |
| `owner` | Owner aplikasi (full access) |
| `basic` | Admin basic (hanya transaksi produk digital) |

### admin\_tier (User)

| Nilai | Deskripsi |
| - | - |
| `none` | Tanpa tier |
| `business` | Tier business |
| `advanced` | Tier advanced |
| `enterprise` | Tier enterprise |

### gateway\_mode (PaymentGatewaySettings)

| Nilai | Deskripsi |
| - | - |
| `sandbox` | Mode testing |
| `production` | Mode live/produksi |

***

## Tabel RBAC — Matriks Permission Kasir

Berikut matriks hak akses untuk operasi kasir berdasarkan peran dalam sistem.

| Operasi | Owner | Admin | Kasir (user) | Viewer |
| - | :-: | :-: | :-: | :-: |
| **Buka halaman kasir** | ✅ | ✅ | ✅ | ❌ |
| **Lihat product grid** | ✅ | ✅ | ✅ | ✅ |
| **Tambah item ke cart** | ✅ | ✅ | ✅ | ❌ |
| **Proses pembayaran (checkout)** | ✅ | ✅ | ✅ | ❌ |
| **Verifikasi pembayaran non-tunai** | ✅ | ✅ | ❌ | ❌ |
| **Split payment** | ✅ | ✅ | ✅ | ❌ |
| **Hold transaction (F4)** | ✅ | ✅ | ✅ | ❌ |
| **Recall held transaction** | ✅ | ✅ | ✅ | ❌ |
| **Void transaksi (≤24 jam)** | ✅ | ✅ | ❌ | ❌ |
| **Refund parsial** | ✅ | ✅ | ❌ | ❌ |
| **Refund penuh** | ✅ | ✅ | ❌ | ❌ |
| **Print ulang receipt (Ctrl+P)** | ✅ | ✅ | ✅ | ❌ |
| **Kirim receipt via email/WA** | ✅ | ✅ | ✅ | ❌ |
| **Kelola produk POS (CRUD)** | ✅ | ✅ | ❌ | ❌ |
| **Kelola kategori POS** | ✅ | ✅ | ❌ | ❌ |
| **Atur diskon/voucher** | ✅ | ✅ | ❌ | ❌ |
| **Kelola member/loyalty** | ✅ | ✅ | ❌ | ❌ |
| **Ubah tier member** | ✅ | ✅ | ❌ | ❌ |
| **Adjustment stok manual** | ✅ | ✅ | ❌ | ❌ |
| **Konfigurasi receipt settings** | ✅ | ✅ | ❌ | ❌ |
| **Konfigurasi printer** | ✅ | ✅ | ✅ | ❌ |
| **Konfigurasi payment gateway** | ✅ | ✅ | ❌ | ❌ |
| **Lihat laporan transaksi** | ✅ | ✅ | ✅ (milik sendiri) | ❌ |
| **Lihat semua transaksi kasir** | ✅ | ✅ | ❌ | ❌ |
| **Export laporan kasir** | ✅ | ✅ | ❌ | ❌ |
| **Offline mode (buat transaksi)** | ✅ | ✅ | ✅ | ❌ |
| **Offline mode (sync)** | ✅ | ✅ | ✅ | ❌ |
| **Hapus transaksi** | ✅ | ✅ | ❌ | ❌ |

**Catatan RBAC:**

* **Owner** memiliki akses penuh ke seluruh fitur kasir dan konfigurasi sistem
* **Admin** memiliki akses yang hampir setara Owner, kecuali beberapa pengaturan sensitif tertentu
* **Kasir (user)** hanya bisa melakukan operasi transaksi dasar — tidak bisa void, refund, atau mengubah konfigurasi
* **Viewer** hanya bisa melihat product grid dan laporan, tidak bisa melakukan transaksi
* Void transaksi dibatasi maksimum 24 jam setelah transaksi dan wajib menyertakan alasan
* Verifikasi pembayaran non-tunai (manual\_transfer) memerlukan role Admin atau Owner
* Pada offline mode, semua role yang diizinkan transaksi bisa membuat dan menyimpan transaksi lokal, namun sinkronisasi memerlukan koneksi aktif


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.