> ## 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.

# Overview

<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: "POS Overview"
description: "Gambaran lengkap sistem Point of Sale (POS) SNISHOP ERP — 10 entitas, 14 server functions, 8 BroadcastChannels, dual POS (personal + company), arsitektur offline-first dengan WAL crash recovery, dan integrasi lintas 7 modul."
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# POS Overview

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

Sistem **Point of Sale (POS)** SNISHOP ERP adalah pusat transaksi penjualan yang mengelola seluruh alur dari kasir hingga laporan. Dirancang untuk bisnis F\&B multi-channel, sistem ini mendukung **8 channel penjualan**, **6 metode pembayaran**, **multi-lokasi**, dan **arsitektur offline-first** dengan Write-Ahead Log untuk crash recovery.

Modul POS terdiri dari **10 entitas**, **14 server functions**, **8 BroadcastChannels** untuk real-time sync, dan **2 terminal kasir** (personal + company) yang totalnya mencakup lebih dari **10.000 baris kode** komponen React.

## Arsitektur Sistem POS

```mermaid theme={null}
graph TD
    subgraph "Channel Penjualan — 8 Channels"
        C1[Offline / Kasir]
        C2[Marketplace]
        C3[Website / Toko Online]
        C4[WhatsApp]
        C5[Reseller]
        C6[B2B]
        C7[GrabFood]
        C8[Social Media]
    end

    subgraph "POS Engine — 2 Terminals"
        E1[Personal POS<br/>POS.jsx — 948 lines<br/>Owner-scoped RLS]
        E2[Company POS Cashier<br/>CompanyPOSCashier.jsx — 6.191 lines<br/>Company-scoped RLS]
    end

    subgraph "Custom Hooks Layer"
        H1[usePOSCart — 326 lines]
        H2[usePOSPayment — 686 lines]
        H3[usePOSDataLoader — 376 lines]
        H4[usePOSSync — 170 lines]
    end

    subgraph "Sync Layer — 8 BroadcastChannels"
        S1[snishop_pos_updates]
        S2[snishop_inventory_updates]
        S3[snishop_product_updates]
        S4[snishop_reports_updates]
        S5[snishop_order_queue]
        S6[snishop_finance_updates]
        S7[snishop_member_updates]
        S8[snishop_receipt_settings_updates]
    end

    subgraph "Backend — 14 Server Functions"
        B1[finalizeCompanyPOSCashierSale]
        B2[voidCompanyPOSTransaction]
        B3[transferInventoryLots]
        B4[approveCashierDiscount]
        B5[manageCustomerLoyalty]
    end

    subgraph "Persistence"
        P1[CompanyPOSTransaction<br/>50+ fields]
        P2[TransactionQueue.js — 241 lines<br/>Write-Ahead Log]
        P3[localStorage<br/>held transactions + WAL]
    end

    C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8 --> E1 & E2
    E1 & E2 --> H1 & H2 & H3 & H4
    H4 --> S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8
    E2 --> B1 & B2 & B3 & B4 & B5
    B1 --> P1
    E2 --> P2 --> P3
```

## Dual POS System

```mermaid theme={null}
graph LR
    subgraph "Personal POS"
        PP[POS.jsx<br/>948 lines]
        PPE[POSTransaction<br/>Owner-scoped]
        PPR[RLS: owner_id = current_user]
    end

    subgraph "Company POS"
        CP[CompanyPOSCashier.jsx<br/>6.191 lines]
        CPE[CompanyPOSTransaction<br/>Company-scoped]
        CPR[RLS: company_id = current_company]
    end

    PP --> PPE --> PPR
    CP --> CPE --> CPR
```

| Aspek | Personal POS | Company POS |
| - | - | - |
| **Komponen** | `POS.jsx` — 948 baris | `CompanyPOSCashier.jsx` — 6.191 baris |
| **Entity** | `POSTransaction` | `CompanyPOSTransaction` |
| **RLS** | `owner_id = current_user.id` | `company_id = current_company.id` |
| **Multi-Outlet** | Tidak | Ya — outlet selector |
| **Split Payment** | Tidak | Ya — `payments[]` array |
| **Hold/Recall** | Tidak | Ya — `heldTransactions[]` |
| **Bluetooth Print** | Tidak | Ya — ESC/POS via BLE |
| **WhatsApp Receipt** | Tidak | Ya |
| **Loyalty** | Basic | Full tier + poin + stamps |
| **Use Case** | Solo owner-operator | Multi-outlet enterprise |

## 10 Entitas POS

| # | Entity | Fields | Deskripsi |
| - | - | - | - |
| 1 | **CompanyPOSTransaction** | 50+ | Transaksi kasir company-scoped dengan full audit trail |
| 2 | **CompanyPOSProduct** | 35+ | Produk POS company-scoped dengan channel\_pricing{} |
| 3 | **CompanyPOSInventory** | 15+ | Stok per lokasi dengan avg\_cost (FIFO weighted) |
| 4 | **CompanyPOSCategory** | 8+ | Kategori produk company-scoped |
| 5 | **CompanyPOSSettings** | 20+ | Pengaturan POS global per company |
| 6 | **POSTransaction** | 30+ | Transaksi kasir personal (owner-scoped) |
| 7 | **POSProduct** | 25+ | Produk POS personal |
| 8 | **POSMember** | 40+ | Database member dengan loyalty data |
| 9 | **POSInventory** | 10+ | Stok produk personal |
| 10 | **POSCategory** | 5+ | Kategori produk personal |

## Halaman-Halaman POS

| Halaman | Komponen | Lines | URL | Fungsi Utama |
| - | - | - | - | - |
| **[Company POS Cashier](/pos/company-pos-cashier)** | `CompanyPOSCashier.jsx` | 6.191 | `/companyposcashier` | Terminal kasir enterprise |
| **[Company POS](/pos/company-pos)** | `CompanyPOS.jsx` | 819 | `/companypos` | Dashboard monitoring multi-outlet |
| **[POS Reports](/pos/pos-reports)** | `POSReports.jsx` | 2.061 | `/pos/pos-reports` | 10 jenis laporan + export |
| **[POS Members](/pos/pos-members)** | `POSMembers.jsx` | 898 | `/pos/pos-members` | Membership & loyalty |
| **[POS Order Queue](/pos/pos-order-queue)** | `POSOrderQueue.jsx` | 862 | `/companypos` | Kanban fulfillment queue |
| **[Order Management](/pos/order-management)** | `OrderManagement.jsx` | 614 | `/companypos` | Multi-channel order tracking |
| **[POS (Personal)](/pos/pos)** | `POS.jsx` | 948 | `/pos` | Kasir personal |
| **[My Orders](/pos/my-orders)** | `MyOrders.jsx` | 344 | `/pos/my-orders` | Personal order tracking |
| **[Payments](/pos/payments)** | — | — | — | Metode pembayaran & split payment |
| **[Sales Channels](/pos/channels)** | — | — | — | Konfigurasi pricing per channel |

## Multi-Channel Sales — 8 Channels

| # | Channel | Deskripsi | Harga | Integrasi |
| - | - | - | - | - |
| 1 | **Offline** | Penjualan langsung di toko | `base_price` | Thermal printer |
| 2 | **Marketplace** | Shopee, Tokopedia, dll | `channel_pricing.marketplace` | Auto-sync stok & order |
| 3 | **Website** | Toko online Quinn of Spicy | `base_price` | Direct checkout |
| 4 | **WhatsApp** | Order via WhatsApp | `base_price` | WA notification |
| 5 | **Reseller** | Penjualan melalui reseller | `channel_pricing.reseller` | Reseller dashboard |
| 6 | **B2B** | Penjualan bisnis ke bisnis | `channel_pricing.b2b` | Invoice + payment term |
| 7 | **Grab** | Order via GrabFood | `channel_pricing.grab` | Auto-accept order |
| 8 | **Social Media** | Order via Instagram/Facebook | `base_price` | Manual input |

### Channel Pricing Gate (A3)

```mermaid theme={null}
flowchart TD
    A[Order dari channel online] --> B{channel_pricing[channel] exists?}
    B -->|Ya| C[Gunakan harga channel]
    B -->|Tidak| D[BLOCK: "Harga belum dikonfigurasi untuk channel ini"]
    D --> E[Admin harus set price di CompanyPOSProduct.channel_pricing]
    C --> F[Lanjutkan transaksi]
```

## Payment Methods — 6 Methods + Split Payment

| # | Metode | Verifikasi | Waktu | Support |
| - | - | - | - | - |
| 1 | **Cash** | Otomatis | Instant | Quick cash buttons |
| 2 | **Transfer Bank** | Manual | 1-30 menit | Multiple bank accounts |
| 3 | **QRIS** | Otomatis | \< 5 detik | Semua bank & e-wallet |
| 4 | **Debit Card** | Otomatis | \< 5 detik | Semua bank |
| 5 | **Credit Card** | Otomatis | \< 5 detik | Visa, Mastercard, JCB |
| 6 | **E-Wallet** | Otomatis | \< 5 detik | GoPay, OVO, Dana, ShopeePay |

### Split Payment (Multi-Tender)

```mermaid theme={null}
sequenceDiagram
    participant K as Kasir
    participant PAY as usePOSPayment (686 lines)
    participant SRV as Server

    K->>PAY: Total Rp 150.000
    K->>PAY: Add Cash Rp 100.000
    PAY->>PAY: remainingAmount = 50.000
    K->>PAY: Add QRIS Rp 50.000
    PAY->>PAY: remainingAmount = 0 → "Lunas" ✅
    K->>PAY: Submit
    PAY->>SRV: payments: [{method:'cash',amount:100000},<br/>{method:'qris',amount:50000}]
```

## 14 Server Functions

| # | Function | Deskripsi | Module |
| - | - | - | - |
| 1 | `finalizeCompanyPOSCashierSale` | Finalisasi transaksi: stok out, jurnal GL, loyalty | POS + Inventory + Finance |
| 2 | `voidCompanyPOSTransaction` | Void transaksi: reverse stok, reverse jurnal (PRD-07) | POS + Inventory + Finance |
| 3 | `transferInventoryLots` | Transfer stok antar lokasi | Inventory |
| 4 | `approveCashierDiscount` | Approval diskon >20% dengan supervisor PIN (A1) | POS |
| 5 | `manageCustomerLoyalty` | Update poin, stamps, tier setelah transaksi | CRM |
| 6 | `listCompanyPOSTransactions` | Query transaksi dengan filter & agregasi | POS |
| 7 | `getCompanyPOSSettings` | Ambil pengaturan POS global | POS |
| 8 | `updateCompanyPOSSettings` | Update pengaturan POS global | POS |
| 9 | `listPOSOrderQueue` | Ambil antrian order untuk fulfillment | POS |
| 10 | `transitionPOSOrderStatus` | Transisi status order di queue | POS |
| 11 | `listCompanyPOSProducts` | Query produk POS dengan filter | POS |
| 12 | `createCompanyPOSProduct` | Tambah produk POS baru | POS |
| 13 | `updateCompanyPOSProduct` | Update produk POS (harga, stok, channel\_pricing) | POS |
| 14 | `deleteCompanyPOSProduct` | Hapus produk POS | POS |

## 8 BroadcastChannels — Real-Time Sync

| # | Channel | Publisher | Subscriber | Latency |
| - | - | - | - | - |
| 1 | `snishop_pos_updates` | CompanyPOSCashier | CompanyPOS, POSReports, POSOrderQueue | \< 100ms |
| 2 | `snishop_inventory_updates` | Inventory module | POS, Manufacturing | \< 100ms |
| 3 | `snishop_product_updates` | Product management | POS, Inventory | \< 100ms |
| 4 | `snishop_reports_updates` | Reports aggregation | Dashboard | \< 100ms |
| 5 | `snishop_order_queue` | POSOrderQueue | Kitchen display | \< 100ms |
| 6 | `snishop_finance_updates` | Finance module | Dashboard, Reports | \< 100ms |
| 7 | `snishop_member_updates` | POSMembers | CompanyPOSCashier | \< 100ms |
| 8 | `snishop_receipt_settings_updates` | Settings | All POS terminals | \< 100ms |

### 3-Tier Sync Strategy

```mermaid theme={null}
graph LR
    subgraph "Tier 1 — Cross-Tab"
        T1[BroadcastChannel<br/>< 100ms<br/>Same browser, different tabs]
    end
    subgraph "Tier 2 — Server Push"
        T2[WebSocket subscribe<br/>< 500ms<br/>Cross-device, same network]
    end
    subgraph "Tier 3 — Polling Fallback"
        T3[HTTP polling<br/>10-120s adaptive<br/>No WebSocket support]
    end

    T1 -->|If fails| T2
    T2 -->|If fails| T3
```

## Transaction Integrity

| Mekanisme | Deskripsi | Implementasi |
| - | - | - |
| **Module Lock** | Cegah double-submit | `isTransactionInProgress` flag |
| **Safety Timeout** | Auto-release lock jika stuck | 60 detik |
| **WAL** | Crash recovery | `TransactionQueue.js` — 241 baris di localStorage |
| **Idempotency Key** | Cegah duplikasi | UUID per transaksi |
| **Stock Validation** | Cek stok real-time | Server-side check before commit |
| **Optimistic UI** | Update UI sebelum confirm | Rollback on error |

### Write-Ahead Log Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Queued: Kasir klik "Bayar"
    Queued --> Writing: Start WAL write
    Writing --> Pending: WAL written to localStorage
    Pending --> Submitting: POST to server
    Submitting --> Committed: Server 200 OK
    Committed --> [*]: Remove from queue

    Pending --> Failed: Network error / timeout
    Failed --> Retrying: Auto-retry (exponential backoff)
    Retrying --> Submitting: Retry attempt
    Failed --> DeadLetter: Max retries exceeded
    DeadLetter --> [*]: Manual intervention required
```

## Discount Approval (A1)

```mermaid theme={null}
sequenceDiagram
    participant K as Kasir
    participant POS as POS System
    participant SV as Supervisor
    participant SRV as Server

    K->>POS: Apply diskon 25%
    POS->>POS: Check: 25% > 20% threshold
    POS->>POS: BLOCK — butuh approval
    K->>SV: Minta PIN supervisor
    SV->>POS: Input PIN
    POS->>SRV: approveCashierDiscount(pin, discount_pct)
    SRV->>SRV: Verify PIN + authority
    SRV-->>POS: Approved ✅
    POS->>POS: Apply diskon 25%
```

## Void Management (PRD-07)

```mermaid theme={null}
sequenceDiagram
    participant M as Manager
    participant POS as POS System
    participant INV as Inventory
    participant FIN as Finance
    participant SRV as Server

    M->>POS: Pilih transaksi → Void
    POS->>POS: Check: within void window?
    POS->>SRV: voidCompanyPOSTransaction(id, reason)
    SRV->>INV: Reverse stock (stock_in++)
    SRV->>FIN: Reverse journal (contra entries)
    SRV->>SRV: Mark transaction as voided
    SRV-->>POS: Void confirmed
    POS->>POS: BroadcastChannel: transaction_voided
    POS-->>M: Receipt: "VOID — {reason}"
```

## Multi-Location

| Fitur | Deskripsi |
| - | - |
| **Stok Terpisah** | Stok per lokasi dikelola secara independen di `CompanyPOSInventory` |
| **Transfer Antar Lokasi** | `transferInventoryLots` — auto stock\_out + stock\_in |
| **Reporting Per Lokasi** | Laporan penjualan & stok bisa difilter per lokasi |
| **Harga Per Lokasi** | Setiap lokasi bisa memiliki harga berbeda (opsional) |
| **Multi-Location Query** | Auto-query stok dari lokasi lain jika out of stock |
| **3 Lokasi Aktif** | Gudang Utama, Toko Offline, Gudang Reserve |

## Peran & Akses

| Role | Kasir | Order | Payment | Laporan | Settings | Void |
| - | - | - | - | - | - | - |
| **Owner** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Admin** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Manager** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| **Kasir** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| **Inventory** | Read-only | Read-only | ❌ | ❌ | ❌ | ❌ |
| **CS** | Read-only | ✅ | ❌ | ❌ | ❌ | ❌ |

## Keyboard Shortcuts

| Shortcut | Fungsi | Context |
| - | - | - |
| `F1` | Search produk | Kasir |
| `F2` | Buka cart | Kasir |
| `F3` | Bayar / Checkout | Kasir |
| `F4` | Hold transaction | Kasir |
| `F5` | Refresh halaman | Global |
| `Esc` | Tutup modal | Global |
| `Ctrl+P` | Print receipt | Setelah transaksi |
| `Ctrl+H` | Hold transaction | Kasir |
| `Ctrl+Shift+C` | Buka Company POS Cashier | Global |

## Integrasi POS dengan Modul Lain

```mermaid theme={null}
graph LR
    POS[POS Transaction] --> INV[Inventory<br/>Stok berkurang<br/>via transferInventoryLots]
    POS --> FIN[Finance<br/>Jurnal otomatis<br/>Revenue + HPP + PPN]
    POS --> CRM[CRM<br/>Poin membership<br/>via manageCustomerLoyalty]
    POS --> RPT[Reports<br/>Data penjualan<br/>10 report types]
    POS --> AI[AI<br/>Forecasting data<br/>Sales pattern]
    POS --> MFG[Manufacturing<br/>Raw material consumption]
    POS --> HR[HR<br/>Kasir performance<br/>Shift report]
```

## Best Practices

1. **Selalu mulai shift** dengan cek koneksi internet dan status printer
2. **Gunakan shortcut keyboard** untuk mempercepat transaksi saat antrian panjang
3. **Cek channel yang aktif** sebelum memproses transaksi untuk menghindari salah input
4. **Lakukan close shift** di akhir shift untuk rekonsiliasi uang kas
5. **Monitor order queue** secara berkala untuk memastikan tidak ada order yang terlewat
6. **Backup data** secara berkala melalui export laporan
7. **Review discount threshold** (20%) secara periodik — naikkan jika terlalu banyak approval bottleneck
8. **Monitor WAL queue** — jika ada transaksi stuck di "DeadLetter", segera investigasi dan resolve manual

***

## Entity Relationship Diagram

```mermaid theme={null}
erDiagram
    CompanyPOSCategory ||--o{ CompanyPOSProduct : "categorizes"
    CompanyPOSProduct ||--o{ CompanyPOSTransaction : "sold in"
    CompanyPOSProduct ||--o{ CompanyPOSInventory : "tracked by"
    CompanyPOSSettings ||--|| CompanyPOSTransaction : "configures"
    CompanyPOSProduct }o--o{ CompanyPOSProduct : "bundle_components"

    POSTransaction }o--o| POSMember : "earns loyalty"
    POSProduct ||--o{ POSTransaction : "sold in"
    POSProduct ||--o{ POSInventory : "tracked by"
    POSCategory ||--o{ POSProduct : "categorizes"

    CompanyPOSTransaction {
        string company_id FK
        string location_id FK
        string transaction_number PK
        string invoice_number
        date transaction_date
        array items
        number subtotal
        number discount_amount
        number tax_amount
        number total
        enum payment_method
        enum payment_status
        enum source
        enum order_status
        enum status
        enum sales_channel
        string customer_id FK
    }

    CompanyPOSProduct {
        string company_id FK
        string name
        string sku PK
        string category FK
        string category_key
        string variant_key
        number price
        number cost
        number stock
        enum product_type
        enum packaging_type
        object channel_pricing
        boolean is_bundle
        array bundle_components
    }

    CompanyPOSInventory {
        string company_id FK
        string product_id FK
        enum type
        number quantity
        number stock_before
        number stock_after
        string reference_id FK
    }

    CompanyPOSCategory {
        string company_id FK
        string name
        string description
        string icon
        string color
    }

    CompanyPOSSettings {
        string company_id FK
        object receipt_settings
    }

    POSTransaction {
        string transaction_number PK
        array items
        number total
        enum payment_method
        enum status
        string customer_id FK
    }

    POSProduct {
        string name
        string sku PK
        string category FK
        number price
        number stock
        boolean is_synced_from_digital
    }

    POSMember {
        string name
        string phone
        enum membership_tier
        number points
        number stamps
        number total_spent
    }

    POSInventory {
        string product_id FK
        enum type
        number quantity
        number stock_before
        number stock_after
        string reference_id FK
    }

    POSCategory {
        string name
        string description
        string icon
        string color
    }
```

***

## Entity Schema Tables

### Company POS Entities (5 Entitas)

#### 1. CompanyPOSTransaction — 48 Fields

Transaksi kasir company-scoped dengan full audit trail. RLS: `company_id = current_company.id`.

| Field | Type | Required | Description |
| - | - | - | - |
| `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 |
| `invoice_number` | string | Tidak | Nomor invoice |
| `transaction_date` | date-time | Tidak | Tanggal dan waktu transaksi |
| `items` | array\<object> | Ya | Item yang dibeli (lihat sub-tabel di bawah) |
| `subtotal` | number | Tidak | Subtotal sebelum diskon & pajak |
| `discount_amount` | number | Tidak | Nominal diskon (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 |
| `total_amount` | number | Tidak | Total akhir (alias untuk `total`) |
| `payment_method` | enum | Tidak | Metode pembayaran — nilai: `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) — ketika kosong/null, `payment_method` scalar adalah sumber kebenaran |
| `payment_status` | enum | Tidak | Status pembayaran — nilai: `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 pelanggan |
| `customer_phone` | string | Tidak | Nomor telepon pelanggan |
| `customer_address` | string | Tidak | Alamat pelanggan |
| `points_earned` | number | Tidak | Poin loyalty yang didapat member (default: 0) |
| `points_used` | number | Tidak | Poin yang dipakai untuk transaksi ini (default: 0) |
| `cashier_id` | string | Tidak | ID karyawan kasir |
| `cashier_name` | string | Tidak | Nama kasir |
| `assigned_to_id` | string | Tidak | ID pengguna yang ditugaskan |
| `assigned_to_name` | string | Tidak | Nama pengguna yang ditugaskan |
| `notes` | string | Tidak | Catatan transaksi |
| `source` | enum | Tidak | Sumber transaksi — nilai: `pos`, `online_catalog`, `website`, `marketplace`, `landing_page`, `whatsapp`, `reseller`, `b2b`, `grab`, `social_media` (default: `pos`) |
| `order_status` | enum | Tidak | Status order — nilai: `pending`, `processing`, `shipped`, `delivered`, `completed`, `cancelled`, `rejected` (default: `pending`) |
| `status` | enum | Tidak | Status transaksi — nilai: `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 | Sales channel ternormalisasi — nilai: `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 | Pengguna yang memverifikasi transaksi |
| `processed_at` | date-time | Tidak | Waktu transaksi diproses |
| `shipped_at` | date-time | Tidak | Waktu transaksi dikirim |
| `delivered_at` | date-time | Tidak | Waktu transaksi diterima |
| `cancelled_at` | date-time | Tidak | Waktu transaksi dibatalkan |

**Sub-skema: `items[]`**

| Field | Type | Required | Description |
| - | - | - | - |
| `product_id` | string | Tidak | ID produk |
| `product_name` | string | Tidak | Nama produk |
| `sku` | string | Tidak | SKU produk |
| `quantity` | number | Tidak | Jumlah yang dibeli |
| `price` | number | Tidak | Harga satuan |
| `discount` | number | Tidak | Diskon per-item (default: 0) |
| `subtotal` | number | Tidak | Subtotal per-item |
| `unit` | string | Tidak | Satuan produk |
| `unit_cost` | number | Tidak | Harga modal per satuan |
| `total_cost` | number | Tidak | Total harga modal per-item |
| `note` | string | Tidak | Catatan per-baris dari kasir (opsional), dipersisten dari payload finalize |

**Sub-skema: `payments[]` (Split Payment)**

| Field | Type | Required | Description |
| - | - | - | - |
| `method` | string | Tidak | Metode pembayaran untuk tender ini |
| `amount` | number | Tidak | Nominal pembayaran tender ini |
| `change_amount` | number | Tidak | Kembalian untuk tender ini (default: 0) |
| `account_id` | string | Tidak | ID akun bank/e-wallet tujuan |
| `reference` | string | Tidak | Referensi bukti transfer |
| `verified_by` | string | Tidak | Pengguna yang memverifikasi tender ini |
| `verified_at` | date-time | Tidak | Waktu verifikasi tender ini |

***

#### 2. CompanyPOSProduct — 40 Fields

Produk POS company-scoped dengan channel pricing, bundle support, dan data quality lock. RLS: `company_id = current_company.id`.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `name` | string | Ya | Nama produk |
| `sku` | string | Tidak | SKU/Barcode produk |
| `category` | string | Tidak | Kategori produk |
| `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 (ukuran, warna, dll) |
| `is_active` | boolean | Tidak | Status aktif produk (default: true) |
| `deactivated_at` | date-time | Tidak | Waktu SKU dinonaktifkan tanpa menghapus histori transaksi dan inventory |
| `deactivated_by` | string | Tidak | Akun yang menonaktifkan SKU untuk audit |
| `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 — nilai: `finished_good`, `raw_material`, `semi_finished`, `packaging_material`, `bundle` (default: `finished_good`) |
| `packaging_type` | enum | Tidak | Jenis kemasan — nilai: `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 — nilai: `room_temperature`, `chilled`, `frozen` (default: `room_temperature`) |
| `is_bundle` | boolean | Tidak | Menandakan paket bundle virtual (default: false) |
| `bundle_components` | array\<object> | Tidak | Komponen satuan SKU penyusun paket bundle |
| `channel_pricing` | object | Tidak | Harga khusus per saluran penjualan |
| `is_locked` | boolean | Tidak | Data terkunci karena sudah ada transaksi — field nama, SKU, satuan tidak bisa diedit (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 untuk perubahan harga setelah lock |
| `sold_count` | number | Tidak | Total jumlah produk terjual (akumulator dari semua transaksi) (default: 0) |

**Sub-skema: `variants[]`**

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Tidak | Nama varian |
| `sku` | string | Tidak | SKU varian |
| `price` | number | Tidak | Harga varian |
| `stock` | number | Tidak | Stok varian |

**Sub-skema: `bundle_components[]`**

| Field | Type | Required | Description |
| - | - | - | - |
| `sku` | string | Ya | SKU komponen |
| `product_id` | string | Tidak | ID produk komponen |
| `name` | string | Tidak | Nama komponen |
| `quantity` | number | Ya | Jumlah komponen per bundle (default: 1) |
| `unit` | string | Tidak | Satuan komponen |

**Sub-skema: `channel_pricing`**

| Field | Type | Required | Description |
| - | - | - | - |
| `offline_pos` | number | Tidak | Harga untuk channel offline POS |
| `marketplace` | number | Tidak | Harga untuk channel marketplace |
| `website` | number | Tidak | Harga untuk channel website |
| `whatsapp` | number | Tidak | Harga untuk channel WhatsApp |
| `reseller` | number | Tidak | Harga untuk channel reseller |
| `b2b` | number | Tidak | Harga untuk channel B2B |
| `grab` | number | Tidak | Harga untuk channel GrabFood |
| `social_media` | number | Tidak | Harga untuk channel social media |

***

#### 3. CompanyPOSInventory — 11 Fields

Stok dan pergerakan inventory per lokasi. RLS: `company_id = current_company.id`.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `product_id` | string | Ya | ID produk |
| `product_name` | string | Tidak | Nama produk |
| `type` | enum | Ya | Tipe pergerakan stok — nilai: `in`, `out`, `adjustment` |
| `quantity` | number | Ya | Jumlah perubahan stok |
| `stock_before` | number | Tidak | Jumlah stok sebelum perubahan |
| `stock_after` | number | Tidak | Jumlah 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 | Pengguna yang melakukan perubahan |

***

#### 4. CompanyPOSCategory — 6 Fields

Kategori produk company-scoped. RLS: `company_id = current_company.id`.

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

***

#### 5. CompanyPOSSettings — 2 Fields (+ 15 nested receipt\_settings)

Pengaturan POS global per company. RLS: `company_id = current_company.id`.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan |
| `description` | string | Tidak | Catatan mengenai konfigurasi sistem kasir (POS) yang digunakan perusahaan (maks 1000 karakter) |
| `receipt_settings` | object | Tidak | Pengaturan struk (lihat sub-tabel di bawah) |

**Sub-skema: `receipt_settings`**

| Field | Type | Required | Description |
| - | - | - | - |
| `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 — nilai: `58mm`, `80mm` (default: `80mm`) |
| `show_logo` | boolean | Tidak | Tampilkan logo di struk (default: true) |
| `show_tax_id` | boolean | Tidak | Tampilkan NPWP di struk (default: false) |
| `show_cashier_name` | boolean | Tidak | Tampilkan nama kasir di struk (default: true) |
| `show_customer_info` | boolean | Tidak | Tampilkan info pelanggan di struk (default: true) |
| `show_item_image` | boolean | Tidak | Tampilkan gambar item di struk (default: false) |
| `header_text` | string | Tidak | Teks header struk (default: `STRUK PEMBELIAN`) |
| `footer_message` | string | Tidak | Pesan footer struk (default: `Terima kasih atas kunjungan Anda!\nBarang yang sudah dibeli tidak dapat ditukar/dikembalikan.`) |
| `print_auto_after_transaction` | boolean | Tidak | Auto-print struk setelah transaksi (default: true) |
| `font_size` | enum | Tidak | Ukuran font struk — nilai: `small`, `medium`, `large` (default: `medium`) |

***

### Personal POS Entities (5 Entitas)

#### 6. POSTransaction — 17 Fields

Transaksi kasir personal (owner-scoped). RLS: `created_by_id = current_user.id`.

| Field | Type | Required | Description |
| - | - | - | - |
| `transaction_number` | string | Ya | Nomor transaksi unik |
| `items` | array\<object> | Ya | Item yang dibeli (lihat sub-tabel di bawah) |
| `subtotal` | number | Tidak | Subtotal sebelum diskon & pajak |
| `discount_amount` | number | Tidak | Nominal diskon (default: 0) |
| `discount_percentage` | number | Tidak | Persentase diskon (default: 0) |
| `tax_amount` | number | Tidak | PPN (default: 0) |
| `total` | number | Ya | Total akhir |
| `payment_method` | enum | Ya | Metode pembayaran — nilai: `cash`, `card`, `transfer`, `ewallet`, `qris` |
| `payment_amount` | number | Tidak | Jumlah uang yang dibayar |
| `change_amount` | number | Tidak | Kembalian (default: 0) |
| `customer_id` | string | Tidak | ID member jika ada |
| `customer_name` | string | Tidak | Nama pelanggan |
| `customer_phone` | string | Tidak | Nomor telepon pelanggan |
| `points_earned` | number | Tidak | Poin yang didapat member (default: 0) |
| `points_used` | number | Tidak | Poin yang dipakai (default: 0) |
| `cashier_name` | string | Tidak | Nama kasir |
| `notes` | string | Tidak | Catatan transaksi |
| `status` | enum | Tidak | Status transaksi — nilai: `completed`, `refunded`, `cancelled` (default: `completed`) |

**Sub-skema: `items[]`**

| Field | Type | Required | Description |
| - | - | - | - |
| `product_id` | string | Tidak | ID produk |
| `product_name` | string | Tidak | Nama produk |
| `sku` | string | Tidak | SKU produk |
| `quantity` | number | Tidak | Jumlah yang dibeli |
| `price` | number | Tidak | Harga satuan |
| `discount` | number | Tidak | Diskon per-item (default: 0) |
| `subtotal` | number | Tidak | Subtotal per-item |

***

#### 7. POSProduct — 16 Fields

Produk POS personal. RLS: owner-scoped.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Ya | Nama produk |
| `sku` | string | Tidak | SKU/Barcode produk |
| `category` | string | Tidak | Kategori produk |
| `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 produk |
| `variants` | array\<object> | Tidak | Varian produk (ukuran, warna, dll) |
| `is_active` | boolean | Tidak | Status aktif (default: true) |
| `tax_rate` | number | Tidak | Persentase pajak 0-100 (default: 0) |
| `supplier` | string | Tidak | Nama supplier |
| `unit` | string | Tidak | Satuan produk (default: `pcs`) |
| `digital_product_id` | string | Tidak | ID dari DigitalProduct jika sinkronisasi |
| `is_synced_from_digital` | boolean | Tidak | Apakah produk disinkronkan dari DigitalProduct (default: false) |
| `last_synced` | date-time | Tidak | Waktu terakhir sinkronisasi |

**Sub-skema: `variants[]`**

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Tidak | Nama varian |
| `sku` | string | Tidak | SKU varian |
| `price` | number | Tidak | Harga varian |
| `stock` | number | Tidak | Stok varian |

***

#### 8. POSMember — 21 Fields

Database member dengan loyalty data. RLS: owner-scoped.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Ya | Nama member |
| `phone` | string | Ya | Nomor telepon |
| `email` | string | Tidak | Email member |
| `address` | string | Tidak | Alamat member |
| `birth_date` | date | Tidak | Tanggal lahir |
| `membership_tier` | enum | Tidak | Level membership — nilai: `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 transaksi (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 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 keaktifan member (default: true) |

***

#### 9. POSInventory — 10 Fields

Stok produk personal. RLS: owner-scoped.

| Field | Type | Required | Description |
| - | - | - | - |
| `product_id` | string | Ya | ID produk |
| `product_name` | string | Tidak | Nama produk |
| `type` | enum | Ya | Tipe pergerakan stok — nilai: `in`, `out`, `adjustment` |
| `quantity` | number | Ya | Jumlah perubahan stok |
| `stock_before` | number | Tidak | Jumlah stok sebelum perubahan |
| `stock_after` | number | Tidak | Jumlah 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 | Pengguna yang melakukan perubahan |

***

#### 10. POSCategory — 5 Fields

Kategori produk personal. RLS: owner-scoped.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Ya | Nama kategori |
| `description` | string | Tidak | Penjelasan mengenai jenis produk dalam kategori (maks 1000 karakter) |
| `icon` | string | Tidak | Icon emoji |
| `color` | string | Tidak | Warna kategori (default: `#3b82f6`) |
| `order` | number | Tidak | Urutan pengurutan (default: 0) |

***

## State Machines — Transaction Lifecycle

### CompanyPOSTransaction: `order_status` Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Order dibuat (kasir/online)
    pending --> processing: Kasir mulai siapkan order
    processing --> shipped: Order diserahkan ke kurir
    shipped --> delivered: Pelanggan menerima order
    delivered --> completed: Transaksi selesai
    pending --> cancelled: Pelanggan batalkan order
    processing --> cancelled: Kasir batalkan order
    processing --> rejected: Order ditolak (stok habis/verifikasi gagal)
    pending --> rejected: Order ditolak otomatis
```

### CompanyPOSTransaction: `status` Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Transaksi dicatat
    pending --> processing: Pembayaran diverifikasi
    processing --> shipped: Barang dikirim
    shipped --> delivered: Barang diterima
    delivered --> completed: Transaksi finalized
    pending --> failed: Pembayaran gagal
    processing --> failed: Error saat proses
    completed --> refunded: Retur/refund diproses
    completed --> cancelled: Void transaksi (PRD-07)
    pending --> cancelled: Dibatalkan sebelum proses
```

### CompanyPOSTransaction: `payment_status` Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Transaksi dimulai
    pending --> pending_verification: Pembayaran non-tunai menunggu verifikasi
    pending_verification --> paid: Verifikasi berhasil
    pending_verification --> rejected: Verifikasi ditolak
    pending --> partially_paid: Split payment belum lunas
    partially_paid --> paid: Semua tender terbayar
    pending --> failed: Pembayaran gagal/timeout
    paid --> refunded: Refund diproses
```

### POSTransaction: `status` Lifecycle (Personal)

```mermaid theme={null}
stateDiagram-v2
    [*] --> completed: Transaksi selesai di kasir
    completed --> refunded: Retur/refund
    completed --> cancelled: Void transaksi
```

### CompanyPOSProduct: Active/Deactivated Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> Active: Produk dibuat (is_active=true)
    Active --> Deactivated: SKU dinonaktifkan (deactivated_at di-set)
    Deactivated --> Active: SKU diaktifkan kembali
    Deactivated --> [*]: Dihapus permanen (jika tidak ada transaksi)

    state Active {
        [*] --> Editable
        Editable --> Locked: Sudah ada transaksi (is_locked=true)
        Locked --> Editable: Admin unlock
    }
```

***

## Enum Reference Tables

### `payment_method` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `cash` | Pembayaran tunai |
| `card` | Kartu debit/kredit |
| `transfer` | Transfer bank manual |
| `ewallet` | E-wallet (GoPay, OVO, Dana, ShopeePay) |
| `qris` | QRIS (semua bank & e-wallet) |
| `saldo` | Pembayaran dari saldo member |
| `mayar` | Via Mayar payment gateway |
| `debt` | Hutang/piutang (B2B) |
| `manual_transfer` | Transfer manual yang perlu verifikasi |
| `midtrans` | Via Midtrans payment gateway |
| `tripay` | Via Tripay payment gateway |
| `stripe` | Via Stripe payment gateway |
| `paypal` | Via PayPal |

### `payment_method` — POSTransaction (Personal)

| Nilai | Deskripsi |
| - | - |
| `cash` | Pembayaran tunai |
| `card` | Kartu debit/kredit |
| `transfer` | Transfer bank |
| `ewallet` | E-wallet |
| `qris` | QRIS |

### `source` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `pos` | Transaksi dari kasir POS |
| `online_catalog` | Katalog online |
| `website` | Website toko online |
| `marketplace` | Marketplace (Shopee, Tokopedia, dll) |
| `landing_page` | Landing page |
| `whatsapp` | Order via WhatsApp |
| `reseller` | Penjualan reseller |
| `b2b` | Penjualan B2B |
| `grab` | Order via GrabFood |
| `social_media` | Order via social media |

### `sales_channel` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `offline_pos` | Kasir offline (default untuk transaksi POS langsung) |
| `offline` | Offline lainnya |
| `website` | Website |
| `online_catalog` | Katalog online |
| `marketplace` | Marketplace |
| `landing_page` | Landing page |
| `whatsapp` | WhatsApp |
| `reseller` | Reseller |
| `b2b` | B2B |
| `grab` | GrabFood |
| `social_media` | Social media |

### `order_status` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `pending` | Order baru dibuat, belum diproses |
| `processing` | Order sedang disiapkan |
| `shipped` | Order telah dikirim |
| `delivered` | Order telah diterima pelanggan |
| `completed` | Transaksi selesai |
| `cancelled` | Order dibatalkan |
| `rejected` | Order ditolak |

### `status` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `pending` | Transaksi dicatat, menunggu proses |
| `processing` | Transaksi sedang diproses |
| `shipped` | Barang telah dikirim |
| `delivered` | Barang telah diterima |
| `completed` | Transaksi selesai dan finalized |
| `failed` | Transaksi gagal |
| `refunded` | Transaksi di-refund |
| `cancelled` | Transaksi dibatalkan/void |

### `payment_status` — CompanyPOSTransaction

| Nilai | Deskripsi |
| - | - |
| `pending` | Menunggu pembayaran |
| `pending_verification` | Pembayaran non-tunai menunggu verifikasi manual |
| `partially_paid` | Split payment belum lunas |
| `paid` | Pembayaran lunas |
| `failed` | Pembayaran gagal |
| `refunded` | Dana dikembalikan |
| `rejected` | Pembayaran ditolak |

### `status` — POSTransaction (Personal)

| Nilai | Deskripsi |
| - | - |
| `completed` | Transaksi selesai |
| `refunded` | Transaksi di-refund |
| `cancelled` | Transaksi dibatalkan |

### `product_type` — CompanyPOSProduct

| Nilai | Deskripsi |
| - | - |
| `finished_good` | Produk jadi siap jual (default) |
| `raw_material` | Bahan baku untuk manufacturing |
| `semi_finished` | Produk setengah jadi |
| `packaging_material` | Material kemasan/packaging |
| `bundle` | Paket bundle virtual (BND-01) |

### `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/bulk |
| `other` | Lainnya |

### `storage_condition` — CompanyPOSProduct

| Nilai | Deskripsi |
| - | - |
| `room_temperature` | Suhu ruang (default) |
| `chilled` | Dingin (chiller) |
| `frozen` | Beku (freezer) |

### `type` — CompanyPOSInventory & POSInventory

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

### `membership_tier` — POSMember

| Nilai | Deskripsi |
| - | - |
| `regular` | Member biasa (default) |
| `silver` | Member silver |
| `gold` | Member gold |
| `platinum` | Member platinum |

### `receipt_width` — CompanyPOSSettings.receipt\_settings

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

### `font_size` — CompanyPOSSettings.receipt\_settings

| Nilai | Deskripsi |
| - | - |
| `small` | Font kecil |
| `medium` | Font sedang (default) |
| `large` | Font besar |


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