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

# Company pos

<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: "Company POS"
description: "Dashboard monitoring POS enterprise — CompanyPOSCashier.jsx (819 baris), multi-outlet metrics, global settings, performance comparison, dan real-time sync via BroadcastChannel."
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# Company POS

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

**Company POS** (`CompanyPOS.jsx` — 819 baris) adalah dashboard Point of Sale di level perusahaan yang memungkinkan manajemen untuk memantau seluruh aktivitas penjualan dari **semua outlet dalam satu tampilan**. Dashboard ini mengkonsolidasi data dari `CompanyPOSTransaction` di seluruh lokasi, menampilkan metrik real-time, perbandingan performa antar outlet, dan pengaturan global yang berlaku untuk semua kasir.

Dirancang untuk **Owner dan Manager** yang butuh gambaran real-time tanpa harus membuka satu per satu outlet, dashboard ini menggunakan **BroadcastChannel sync** untuk memastikan data selalu terkini tanpa polling berlebihan.

## Arsitektur Monitoring

```mermaid theme={null}
graph TB
    subgraph "CompanyPOS.jsx — 819 lines"
        direction TB
        HEADER[Header: Company Selector + Date Range]
        METRICS[KPI Cards<br/>4 metrics]
        CHARTS[Charts Section<br/>Revenue + Transactions]
        TABLE[Outlet Table<br/>Per-location breakdown]
        SETTINGS[Global Settings Panel]
    end

    subgraph "Data Fetching"
        SWR[SWR + React Query<br/>Stale-while-revalidate]
        BC[BroadcastChannel<br/>snishop_pos_updates]
        POLL[Polling Fallback<br/>30s interval]
    end

    subgraph "Server Functions"
        F1[listCompanyPOSTransactions<br/>+ aggregation]
        F2[getCompanyPOSSettings<br/>global config]
        F3[updateCompanyPOSSettings<br/>push to all outlets]
    end

    SWR --> F1 & F2
    BC --> SWR
    POLL --> SWR
    F1 --> METRICS & CHARTS & TABLE
    F2 --> SETTINGS
    F3 --> SETTINGS
```

## Fitur Utama

| Fitur | Deskripsi | Implementasi |
| - | - | - |
| **Multi-Outlet Monitoring** | Pantau semua outlet dari satu layar | Table row per outlet dengan status badge |
| **Real-time Metrics** | 4 KPI cards dengan auto-refresh | BroadcastChannel `snishop_pos_updates` |
| **Global Settings** | Pengaturan POS global untuk semua outlet | `CompanyPOSSettings` entity |
| **Performance Comparison** | Perbandingan performa antar outlet | Side-by-side charts |
| **Consolidated Reports** | Akses cepat ke laporan konsolidasi | Link ke POSReports.jsx |
| **Status Alerts** | Notifikasi jika ada outlet offline | Last sync timestamp check |
| **Quick Actions** | Aksi cepat: buka kasir, lihat order, export | Direct navigation links |

## Komponen Dashboard

### 1. KPI Cards — 4 Metrik Utama

```mermaid theme={null}
graph LR
    subgraph "KPI Card 1"
        R[Total Revenue<br/>Rp 12.450.000<br/>↑ 12% vs yesterday]
    end
    subgraph "KPI Card 2"
        T[Total Transaksi<br/>284<br/>↑ 8% vs yesterday]
    end
    subgraph "KPI Card 3"
        A[Avg Order Value<br/>Rp 43.838<br/>↑ 3.7% vs yesterday]
    end
    subgraph "KPI Card 4"
        O[Outlets Active<br/>3/3 Online<br/>All synced]
    end
```

| KPI | Sumber Data | Refresh | Format |
| - | - | - | - |
| **Total Revenue** | `SUM(total_amount)` dari semua outlet hari ini | Real-time via BroadcastChannel | Rupiah + % change vs yesterday |
| **Total Transaksi** | `COUNT(id)` dari semua outlet hari ini | Real-time | Number + % change |
| **Avg Order Value** | `AVG(total_amount)` dari semua outlet | Real-time | Rupiah + % change |
| **Outlets Active** | Check `last_sync` per outlet (\< 5 min = online) | 30s polling | N/M Online badge |

### 2. Ringkasan Outlet

| Kolom | Deskripsi | Sumber |
| - | - | - |
| **Nama Outlet** | Nama cabang/outlet | `CompanyPOSInventory.location_id` |
| **Status** | Online / Offline / Warning | Last sync \< 5min / > 30min / between |
| **Penjualan Hari Ini** | Total revenue hari ini | `SUM(total_amount) WHERE date = today` |
| **Transaksi** | Jumlah transaksi hari ini | `COUNT(*) WHERE date = today` |
| **Avg Order Value** | Rata-rata nilai per transaksi | `AVG(total_amount)` |
| **Kasir Aktif** | Jumlah kasir yang sedang online | Active sessions count |
| **Last Sync** | Kapan terakhir data di-sync | `MAX(updated_at)` |

### 3. Grafik Perbandingan

| Grafik | Tipe | Periode | Insight |
| - | - | - | - |
| **Revenue per Outlet** | Grouped bar | Harian/Mingguan/Bulanan | Outlet mana yang paling menghasilkan |
| **Transaksi per Outlet** | Stacked bar | Harian/Mingguan | Volume transaksi per outlet |
| **Tren Penjualan** | Multi-line | 7/30/90 hari | Pola pertumbuhan per outlet |
| **Top Products** | Horizontal bar | Bulanan | Produk terlaris di semua outlet |
| **Channel Distribution** | Pie | Custom | Proporsi channel per outlet |

### 4. Pengaturan Global

| Pengaturan | Entity | Deskripsi |
| - | - | - |
| **Metode Pembayaran** | `CompanyPOSSettings.allowed_payments[]` | Aktifkan/nonaktifkan metode untuk semua outlet |
| **Tarif Pajak** | `CompanyPOSSettings.tax_rate` | Konfigurasi pajak global (default 10%) |
| **Diskon Maksimum** | `CompanyPOSSettings.max_discount_pct` | Batas diskon tanpa approval (default 20%) |
| **Receipt Template** | `CompanyPOSSettings.receipt_template` | Template struk untuk semua outlet |
| **Jam Operasional** | `CompanyPOSSettings.operating_hours` | Jam buka/tutup per outlet |
| **Low Stock Threshold** | `CompanyPOSSettings.low_stock_alert` | Alert threshold untuk stok menipis |

## Cara Akses

| Metode | Detail |
| - | - |
| **URL** | `/companypos` |
| **Sidebar** | Menu **POS** → **Company POS** |

## Flow Penggunaan

```mermaid theme={null}
flowchart TD
    A[Buka Company POS] --> B[Load KPI cards + outlet table]
    B --> C{Pilih aksi}
    C -->|Monitor| D[Klik outlet untuk detail]
    C -->|Settings| E[Buka panel Pengaturan]
    C -->|Report| F[Filter tanggal & export]
    C -->|Compare| G[Select 2+ outlets → side by side]

    D --> H[Lihat detail transaksi outlet]
    E --> I[Ubah metode pembayaran/pajak/diskon]
    I --> J[updateCompanyPOSSettings]
    J --> K[BroadcastChannel push ke semua kasir]
    F --> L[Export ke Excel/PDF]
```

## Real-Time Sync Architecture

```mermaid theme={null}
sequenceDiagram
    participant CP as CompanyPOS.jsx
    participant BC as BroadcastChannel<br/>snishop_pos_updates
    participant CS as CompanyPOSCashier<br/>(other tabs)
    participant SRV as Server

    Note over CP: Initial load via SWR
    CP->>SRV: listCompanyPOSTransactions(today)
    SRV-->>CP: Aggregated data → render KPIs

    Note over CS: Kasir A finalize transaction
    CS->>SRV: finalizeCompanyPOSCashierSale(...)
    SRV-->>SRV: Save + update aggregates

    Note over CS: BroadcastChannel post
    CS->>BC: post({type:'transaction_completed',<br/>outlet_id, amount, timestamp})
    BC-->>CP: Receive update
    CP->>CP: Increment total_revenue,<br/>transaction_count
    CP->>CP: Re-render KPI cards
```

| Channel | Event | Effect |
| - | - | - |
| `snishop_pos_updates` | `transaction_completed` | Increment revenue + count |
| `snishop_pos_updates` | `transaction_voided` | Decrement revenue + count |
| `snishop_inventory_updates` | `stock_changed` | Update stock alert badge |
| `snishop_product_updates` | `price_changed` | Refresh pricing data |

## Filter & Export

| Filter | Opsi |
| - | - |
| **Periode** | Hari ini, Kemarin, Minggu ini, Bulan ini, Custom range |
| **Outlet** | Semua outlet, atau pilih outlet spesifik |
| **Kasir** | Semua kasir, atau pilih kasir spesifik |
| **Channel** | Offline, Marketplace, Website, WhatsApp, dll |

| Export Format | Kegunaan |
| - | - |
| **Excel (.xlsx)** | Analisis lebih lanjut, pivot table |
| **PDF** | Presentasi, arsip, laporan ke stakeholder |
| **CSV** | Import ke sistem lain |

## Tips

* **Gunakan fitur perbandingan** untuk mengidentifikasi outlet yang performanya terbaik dan yang perlu perhatian lebih
* **Pastikan semua outlet online** agar data yang tampil selalu akurat dan real-time
* **Manfaatkan laporan konsolidasi** untuk presentasi ke stakeholder tanpa perlu mengumpulkan data manual dari tiap cabang
* **Set alert untuk outlet offline** supaya kamu langsung tahu jika ada outlet yang mengalami masalah koneksi
* **Review avg order value** per outlet untuk mengidentifikasi peluang upselling
* **Cek Last Sync** secara berkala — jika ada outlet dengan sync > 30 menit, investigasi koneksi jaringan

***

## Entity Relationship Diagram

Berikut adalah relasi antar entitas Company POS dalam sistem SNISHOP ERP. Setiap outlet menerbitkan transaksi yang merujuk ke produk, kategori, dan inventori milik perusahaan, dengan pengaturan global yang diatur melalui `CompanyPOSSettings`.

```mermaid theme={null}
erDiagram
    CompanyPOSSettings ||--o{ CompanyPOSProduct : "mengatur global"
    CompanyPOSSettings ||--o{ CompanyPOSCategory : "mengatur global"
    CompanyPOSCategory ||--o{ CompanyPOSProduct : "mengelompokkan"
    CompanyPOSProduct ||--o{ CompanyPOSTransaction : "dijual melalui"
    CompanyPOSProduct ||--o{ CompanyPOSInventory : "pergerakan stok"
    CompanyPOSTransaction }o--|| CompanyPOSInventory : "memicu pengurangan"
    CompanyPOSTransaction }o--o| POSMember : "melibatkan member"

    CompanyPOSSettings {
        string company_id PK
        string description
        object receipt_settings
    }

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

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

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

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

***

## Entity Schema Tables

### CompanyPOSTransaction — Schema Lengkap

Entitas transaksi POS level perusahaan. Setiap transaksi dari kasir outlet dicatat di sini dengan informasi lengkap mengenai item, pembayaran, pelanggan, dan status pengiriman.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | `string` | **Ya** | — | ID perusahaan pemilik transaksi |
| `location_id` | `string` | Tidak | — | ID lokasi gudang/outlet tempat transaksi terjadi |
| `location_name` | `string` | Tidak | — | Nama lokasi outlet (denormalized untuk performa query) |
| `transaction_number` | `string` | **Ya** | — | Nomor transaksi unik (format: `TRX-YYYYMMDD-XXXX`) |
| `invoice_number` | `string` | Tidak | — | Nomor invoice yang dicetak pada struk |
| `transaction_date` | `date-time` | Tidak | — | Tanggal dan waktu transaksi dilakukan |
| `items` | `array<Item>` | **Ya** | — | Daftar item yang dibeli (minimal 1 item) |
| `items[].product_id` | `string` | Tidak | — | ID produk yang dibeli |
| `items[].product_name` | `string` | Tidak | — | Nama produk (denormalized) |
| `items[].sku` | `string` | Tidak | — | SKU produk saat transaksi |
| `items[].quantity` | `number` | Tidak | — | Jumlah unit yang dibeli |
| `items[].price` | `number` | Tidak | — | Harga satuan saat transaksi |
| `items[].discount` | `number` | Tidak | `0` | Diskon per-baris item |
| `items[].subtotal` | `number` | Tidak | — | Subtotal per-baris (quantity x price - discount) |
| `items[].unit` | `string` | Tidak | — | Satuan produk (pcs, kg, dll) |
| `items[].unit_cost` | `number` | Tidak | — | HPP per unit saat transaksi |
| `items[].total_cost` | `number` | Tidak | — | Total HPP untuk baris ini (unit\_cost x quantity) |
| `items[].note` | `string` | Tidak | — | Catatan per-baris dari kasir (misal: "tidak pakai sambal") |
| `subtotal` | `number` | Tidak | — | Subtotal keseluruhan sebelum diskon & pajak |
| `discount_amount` | `number` | Tidak | `0` | Nominal diskon transaksi |
| `discount_percentage` | `number` | Tidak | `0` | Persentase diskon (0-100) |
| `tax_amount` | `number` | Tidak | `0` | PPN — mode inclusive: sudah termasuk di total; exclusive: tambahan di atas total |
| `total` | `number` | **Ya** | — | Total akhir setelah diskon dan pajak |
| `total_amount` | `number` | Tidak | — | Alias dari `total` (backward compatibility) |
| `payment_method` | `enum` | Tidak | — | Metode pembayaran utama: `cash`, `card`, `transfer`, `ewallet`, `qris`, `saldo`, `mayar`, `debt`, `manual_transfer`, `midtrans`, `tripay`, `stripe`, `paypal` |
| `payment_amount` | `number` | Tidak | — | Jumlah uang yang dibayarkan |
| `payments` | `array<Payment>` | Tidak | — | Detail pembayaran multi-tender (split payment) |
| `payments[].method` | `string` | Tidak | — | Metode pembayaran per-tender |
| `payments[].amount` | `number` | Tidak | — | Nominal per-tender |
| `payments[].change_amount` | `number` | Tidak | `0` | Kembalian per-tender |
| `payments[].account_id` | `string` | Tidak | — | ID akun bank/e-wallet tujuan |
| `payments[].reference` | `string` | Tidak | — | Nomor referensi transfer |
| `payments[].verified_by` | `string` | Tidak | — | User yang memverifikasi pembayaran ini |
| `payments[].verified_at` | `date-time` | Tidak | — | Waktu verifikasi pembayaran ini |
| `payment_status` | `enum` | Tidak | `pending` | Status pembayaran: `pending`, `pending_verification`, `partially_paid`, `paid`, `failed`, `refunded`, `rejected` |
| `paid_amount` | `number` | Tidak | `0` | Nominal pembayaran aktual yang telah diverifikasi |
| `remaining_amount` | `number` | Tidak | `0` | Sisa kekurangan tagihan (untuk pembayaran bertahap) |
| `excess_amount` | `number` | Tidak | `0` | Kelebihan nominal transfer |
| `change_amount` | `number` | Tidak | `0` | Kembalian untuk pembayaran tunai |
| `payment_reference` | `string` | Tidak | — | No. referensi bukti transfer/QRIS untuk pembayaran non-tunai |
| `payment_verified_by` | `string` | Tidak | — | Nama user yang memverifikasi pembayaran non-tunai |
| `payment_verified_at` | `date-time` | Tidak | — | Waktu verifikasi pembayaran non-tunai |
| `customer_id` | `string` | Tidak | — | ID member/pelanggan jika terdaftar |
| `customer_name` | `string` | Tidak | — | Nama pelanggan (denormalized) |
| `customer_phone` | `string` | Tidak | — | Nomor telepon pelanggan |
| `customer_address` | `string` | Tidak | — | Alamat pengiriman pelanggan |
| `points_earned` | `number` | Tidak | `0` | Poin reward yang didapat member dari transaksi ini |
| `points_used` | `number` | Tidak | `0` | Poin yang dipakai untuk membayar (potong harga) |
| `cashier_id` | `string` | Tidak | — | ID karyawan kasir yang memproses |
| `cashier_name` | `string` | Tidak | — | Nama kasir (denormalized) |
| `assigned_to_id` | `string` | Tidak | — | ID user yang ditugaskan menangani order ini |
| `assigned_to_name` | `string` | Tidak | — | Nama user yang ditugaskan |
| `notes` | `string` | Tidak | — | Catatan umum transaksi |
| `source` | `enum` | Tidak | `pos` | Sumber transaksi: `pos`, `online_catalog`, `website`, `marketplace`, `landing_page`, `whatsapp`, `reseller`, `b2b`, `grab`, `social_media` |
| `order_status` | `enum` | Tidak | `pending` | Status order: `pending`, `processing`, `shipped`, `delivered`, `completed`, `cancelled`, `rejected` |
| `status` | `enum` | Tidak | `completed` | Status transaksi: `pending`, `processing`, `shipped`, `delivered`, `completed`, `failed`, `refunded`, `cancelled` |
| `tracking_number` | `string` | Tidak | — | Nomor resi pengiriman |
| `shipping_address` | `string` | Tidak | — | Alamat pengiriman |
| `sales_channel` | `enum` | Tidak | `offline_pos` | Normalized sales channel: `offline_pos`, `offline`, `website`, `online_catalog`, `marketplace`, `landing_page`, `whatsapp`, `reseller`, `b2b`, `grab`, `social_media` |
| `metadata` | `object` | Tidak | — | Data tambahan (online order info, metadata marketplace, dll) |
| `verified_at` | `date-time` | Tidak | — | Waktu transaksi diverifikasi |
| `verified_by` | `string` | Tidak | — | User yang memverifikasi |
| `processed_at` | `date-time` | Tidak | — | Waktu transaksi mulai 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 |

**RLS Policy**: Create/Read/Update/Delete dibatasi berdasarkan `company_id` yang sesuai dengan `active_company_id` user, atau `created_by_id` sama dengan `user.id`, atau user memiliki role `admin`.

***

### CompanyPOSProduct — Schema Lengkap

Entitas produk POS level perusahaan. Mendukung multi-channel pricing, bundle virtual, data locking untuk audit trail, dan klasifikasi kanonikal berdasarkan kategori dan varian.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | `string` | **Ya** | — | ID perusahaan pemilik produk |
| `name` | `string` | **Ya** | — | Nama produk |
| `sku` | `string` | Tidak | — | SKU/Barcode produk (unique per company) |
| `category` | `string` | Tidak | — | Kategori produk (legacy, bebas) |
| `category_key` | `string` | Tidak | — | Kategori kanonikal: `kecil`, `besar`, `pouch`, `bundle`, `snack` |
| `category_name` | `string` | Tidak | — | Label display untuk kategori kanonikal |
| `variant_key` | `string` | Tidak | — | Varian kanonikal: `original`, `extra_spicy`, `bundle` |
| `variant_name` | `string` | Tidak | — | Label display untuk varian kanonikal |
| `variant_label` | `string` | Tidak | — | Gabungan kategori + varian untuk POS/inventory/manufacturing |
| `size_grams` | `number` | Tidak | — | Ukuran kanonikal produk dalam gram |
| `base_product_key` | `string` | Tidak | — | Kunci produk utama sebelum dikategorikan/divariasikan |
| `description` | `string` | Tidak | — | Deskripsi detail produk |
| `price` | `number` | **Ya** | — | Harga jual default (Rupiah) |
| `cost` | `number` | Tidak | — | Harga modal/HPP per unit |
| `stock` | `number` | Tidak | `0` | Stok saat ini (akumulasi dari inventory movements) |
| `min_stock` | `number` | Tidak | `5` | Minimum stok sebelum alert triggered |
| `image_url` | `string` | Tidak | — | URL gambar utama produk |
| `gallery` | `array<string>` | Tidak | — | Galeri foto produk (maks 20 foto) |
| `variants` | `array<Variant>` | Tidak | — | Varian produk (ukuran, rasa, dll) |
| `variants[].name` | `string` | Tidak | — | Nama varian |
| `variants[].sku` | `string` | Tidak | — | SKU varian |
| `variants[].price` | `number` | Tidak | — | Harga varian |
| `variants[].stock` | `number` | Tidak | — | Stok varian |
| `is_active` | `boolean` | Tidak | `true` | Status aktif produk |
| `deactivated_at` | `date-time` | Tidak | — | Waktu SKU dinonaktifkan (soft-delete tanpa hapus histori) |
| `deactivated_by` | `string` | Tidak | — | Akun yang menonaktifkan SKU (audit trail) |
| `tax_rate` | `number` | Tidak | `0` | Persentase pajak per-produk (0-100) |
| `supplier` | `string` | Tidak | — | Nama supplier utama |
| `unit` | `string` | Tidak | `pcs` | Satuan produk (pcs, kg, box, dll) |
| `variant` | `string` | Tidak | — | Varian produk (Pedas, Manis, Gurih, dll — legacy) |
| `barcode` | `string` | Tidak | — | Barcode / QR Code produk (EAN-13) |
| `product_type` | `enum` | Tidak | `finished_good` | Tipe produk: `finished_good`, `raw_material`, `semi_finished`, `packaging_material`, `bundle` |
| `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 | `room_temperature` | Kondisi penyimpanan: `room_temperature`, `chilled`, `frozen` |
| `is_bundle` | `boolean` | Tidak | `false` | Menandakan paket bundle virtual (BND-01) |
| `bundle_components` | `array<BundleComponent>` | Tidak | — | Komponen SKU penyusun paket bundle |
| `bundle_components[].sku` | `string` | **Ya** | — | SKU komponen bundle |
| `bundle_components[].product_id` | `string` | Tidak | — | ID produk komponen |
| `bundle_components[].name` | `string` | Tidak | — | Nama komponen |
| `bundle_components[].quantity` | `number` | Tidak | `1` | Jumlah satuan komponen dalam bundle |
| `bundle_components[].unit` | `string` | Tidak | — | Satuan komponen |
| `channel_pricing` | `object` | Tidak | — | Harga khusus per saluran penjualan (CHN-02) |
| `channel_pricing.offline_pos` | `number` | Tidak | — | Harga di kasir offline |
| `channel_pricing.marketplace` | `number` | Tidak | — | Harga di marketplace |
| `channel_pricing.website` | `number` | Tidak | — | Harga di website |
| `channel_pricing.whatsapp` | `number` | Tidak | — | Harga via WhatsApp |
| `channel_pricing.reseller` | `number` | Tidak | — | Harga untuk reseller |
| `channel_pricing.b2b` | `number` | Tidak | — | Harga B2B/korporat |
| `channel_pricing.grab` | `number` | Tidak | — | Harga via GrabFood |
| `channel_pricing.social_media` | `number` | Tidak | — | Harga via social media |
| `is_locked` | `boolean` | Tidak | `false` | Data terkunci karena sudah ada transaksi (DQ-01) |
| `locked_at` | `date-time` | Tidak | — | Waktu penguncian data (DQ-01) |
| `locked_by` | `string` | Tidak | — | Email user yang mengunci data (DQ-01) |
| `lock_reason` | `string` | Tidak | — | Alasan penguncian atau reason code untuk perubahan harga (DQ-01) |
| `sold_count` | `number` | Tidak | `0` | Total jumlah produk terjual (akumulator dari semua transaksi) |

***

### CompanyPOSInventory — Schema Lengkap

Entitas pergerakan stok (inventory movement) level perusahaan. Setiap transaksi POS, restock, adjustment, atau stock opname tercatat di sini dengan snapshot stok sebelum dan sesudah pergerakan.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | `string` | **Ya** | — | ID perusahaan |
| `product_id` | `string` | **Ya** | — | ID produk yang bergerak |
| `product_name` | `string` | Tidak | — | Nama produk (denormalized) |
| `type` | `enum` | **Ya** | — | Tipe pergerakan: `in` (masuk), `out` (keluar), `adjustment` (penyesuaian) |
| `quantity` | `number` | **Ya** | — | Jumlah perubahan stok (positif) |
| `stock_before` | `number` | Tidak | — | Stok sebelum pergerakan |
| `stock_after` | `number` | Tidak | — | Stok setelah pergerakan |
| `reason` | `string` | Tidak | — | Alasan pergerakan (pembelian, penjualan, rusak, expired, stock opname, dll) |
| `reference_id` | `string` | Tidak | — | ID transaksi terkait (CompanyPOSTransaction, PurchaseOrder, dll) |
| `notes` | `string` | Tidak | — | Catatan tambahan |
| `performed_by` | `string` | Tidak | — | User yang melakukan pergerakan stok |

***

### CompanyPOSSettings — Schema Lengkap

Entitas pengaturan global POS level perusahaan. Diterapkan ke semua outlet secara otomatis melalui BroadcastChannel push.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | `string` | **Ya** | — | ID perusahaan |
| `description` | `string` | Tidak | — | Catatan mengenai konfigurasi POS (maks 1000 karakter) |
| `receipt_settings` | `object` | Tidak | — | Konfigurasi struk/receipt |
| `receipt_settings.logo_url` | `string` | Tidak | — | URL logo perusahaan untuk struk |
| `receipt_settings.business_name` | `string` | Tidak | — | Nama bisnis yang tampil di struk |
| `receipt_settings.business_address` | `string` | Tidak | — | Alamat lengkap di struk |
| `receipt_settings.business_phone` | `string` | Tidak | — | Nomor telepon di struk |
| `receipt_settings.business_email` | `string` | Tidak | — | Email bisnis di struk |
| `receipt_settings.tax_id` | `string` | Tidak | — | NPWP/Tax ID perusahaan |
| `receipt_settings.receipt_width` | `enum` | Tidak | `80mm` | Ukuran kertas struk: `58mm`, `80mm` |
| `receipt_settings.show_logo` | `boolean` | Tidak | `true` | Tampilkan logo di struk |
| `receipt_settings.show_tax_id` | `boolean` | Tidak | `false` | Tampilkan NPWP di struk |
| `receipt_settings.show_cashier_name` | `boolean` | Tidak | `true` | Tampilkan nama kasir di struk |
| `receipt_settings.show_customer_info` | `boolean` | Tidak | `true` | Tampilkan info pelanggan di struk |
| `receipt_settings.show_item_image` | `boolean` | Tidak | `false` | Tampilkan gambar item di struk |
| `receipt_settings.header_text` | `string` | Tidak | `STRUK PEMBELIAN` | Teks header struk |
| `receipt_settings.footer_message` | `string` | Tidak | (pesan default) | Pesan footer struk |
| `receipt_settings.print_auto_after_transaction` | `boolean` | Tidak | `true` | Auto-print setelah transaksi selesai |
| `receipt_settings.font_size` | `enum` | Tidak | `medium` | Ukuran font struk: `small`, `medium`, `large` |

***

### CompanyPOSCategory — Schema Lengkap

Entitas kategori produk POS level perusahaan. Digunakan untuk mengelompokkan produk agar mudah dicari di kasir dan laporan.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | `string` | **Ya** | — | ID perusahaan |
| `name` | `string` | **Ya** | — | Nama kategori (misal: "Sambal", "Minuman", "Bundle") |
| `description` | `string` | Tidak | — | Penjelasan kategori (maks 1000 karakter) |
| `icon` | `string` | Tidak | — | Icon emoji untuk tampilan visual |
| `color` | `string` | Tidak | `#3b82f6` | Warna hex untuk badge/tag kategori |
| `order` | `number` | Tidak | `0` | Urutan tampil di UI kasir |

***

## State Machine — Siklus Hidup Transaksi

Transaksi POS melalui beberapa tahap dari pembuatan hingga penyelesaian. State machine berikut menggambarkan siklus lengkap termasuk path untuk pembayaran non-tunai yang memerlukan verifikasi, refund, dan pembatalan.

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

    state "Transaksi Aktif" as ActiveGroup {
        Pending --> Processing: Finalize & submit pembayaran
        Processing --> PendingVerification: Pembayaran non-tunai<br/>(transfer/QRIS)
        PendingVerification --> Paid: Verifikasi oleh manager/kasir senior
        PendingVerification --> Rejected: Bukti transfer tidak valid
        Processing --> PartiallyPaid: Split payment<br/>belum lengkap
        PartiallyPaid --> Paid: Semua tender terbayar
        Processing --> Paid: Pembayaran tunai/ewallet<br/>langsung valid
    }

    Paid --> Completed: Order selesai<br/>(barang diserahkan)
    Completed --> Refunded: Customer return<br/>& refund disetujui
    Paid --> Failed: Payment gateway<br/>timeout/error
    Processing --> Cancelled: Kasir batalkan<br/>sebelum finalize
    Pending --> Cancelled: Customer batal<br/>sebelum bayar
    Rejected --> Cancelled: Pembayaran ditolak<br/>& transaksi dibatalkan

    Refunded --> [*]
    Cancelled --> [*]
    Failed --> [*]

    note right of Processing
        Status order terpisah:
        pending → processing →
        shipped → delivered →
        completed
    end note

    note right of Paid
        payment_status = "paid"
        paid_amount >= total
        remaining_amount = 0
    end note
```

### Mapping Status Fields

Transaksi memiliki dua field status yang berjalan paralel:

| Field | Enum Values | Fungsi |
| - | - | - |
| `payment_status` | `pending` → `pending_verification` → `partially_paid` → `paid` / `failed` / `refunded` / `rejected` | Melacak status pembayaran |
| `order_status` | `pending` → `processing` → `shipped` → `delivered` → `completed` / `cancelled` / `rejected` | Melacak status pemenuhan order |
| `status` | `pending` → `processing` → `shipped` → `delivered` → `completed` / `failed` / `refunded` / `cancelled` | Status agregat transaksi |

### Timestamp Lifecycle

Setiap transisi status mencatat timestamp untuk audit trail:

```mermaid theme={null}
graph LR
    A[transaction_date<br/>Dibuat] --> B[verified_at<br/>Diverifikasi]
    B --> C[processed_at<br/>Diproses]
    C --> D[shipped_at<br/>Dikirim]
    D --> E[delivered_at<br/>Diterima]
    E --> F[completed<br/>Selesai]

    A -.-> G[cancelled_at<br/>Dibatalkan<br/>bisa dari state manapun]
```

***

## Sequence Diagram — Multi-Outlet Monitoring Flow

### Flow 1: Dashboard Load & Aggregation

```mermaid theme={null}
sequenceDiagram
    participant Mgr as Manager/Owner
    participant CP as CompanyPOS.jsx
    participant SWR as SWR Cache
    participant API as Server Functions
    participant DB as Database

    Mgr->>CP: Navigasi ke /companypos
    CP->>SWR: Check cache untuk listCompanyPOSTransactions
    alt Cache miss / stale
        CP->>API: listCompanyPOSTransactions(today, all_outlets)
        API->>DB: SELECT SUM(total), COUNT(*), AVG(total)<br/>GROUP BY location_id<br/>WHERE transaction_date = today
        DB-->>API: Aggregated rows per outlet
        API->>API: Compute KPI metrics:<br/>total_revenue, tx_count, avg_order_value
        API->>DB: SELECT last_sync per outlet<br/>FROM CompanyPOSInventory
        DB-->>API: Last sync timestamps
        API-->>CP: { kpis, outlet_summary, charts_data }
        CP->>CP: Render 4 KPI cards + outlet table + charts
    else Cache hit (fresh)
        SWR-->>CP: Return cached data
        CP->>CP: Render immediately
    end
    CP->>CP: Start BroadcastChannel listener<br/>(snishop_pos_updates)
    CP->>CP: Start polling fallback (30s interval)
```

### Flow 2: Real-Time Update dari Outlet

```mermaid theme={null}
sequenceDiagram
    participant Kasir as Kasir Outlet A
    participant Cashier as CompanyPOSCashier.jsx
    participant BC as BroadcastChannel
    participant CP as CompanyPOS.jsx
    participant API as Server Functions
    participant DB as Database

    Kasir->>Cashier: Scan produk & finalize transaksi
    Cashier->>API: finalizeCompanyPOSCashierSale(items, payment)
    API->>DB: INSERT CompanyPOSTransaction
    API->>DB: UPDATE CompanyPOSInventory<br/>(stock_out untuk setiap item)
    API->>DB: UPDATE CompanyPOSProduct.sold_count += qty
    API-->>Cashier: { success, transaction }

    Cashier->>BC: post({<br/>  type: 'transaction_completed',<br/>  outlet_id: 'outlet_a',<br/>  amount: 285000,<br/>  items_count: 3,<br/>  timestamp: now()<br/>})
    BC-->>CP: Receive broadcast event
    CP->>CP: Increment total_revenue += 285000
    CP->>CP: Increment transaction_count += 1
    CP->>CP: Recompute avg_order_value
    CP->>CP: Update outlet_a row:<br/>penjualan_hari_ini, transaksi, last_sync
    CP->>CP: Re-render KPI cards + outlet table
    Note over CP: Tanpa reload halaman,<br/>tanpa polling ke server
```

### Flow 3: Global Settings Push

```mermaid theme={null}
sequenceDiagram
    participant Mgr as Manager
    participant CP as CompanyPOS.jsx
    participant API as Server Functions
    participant DB as Database
    participant BC as BroadcastChannel
    participant C1 as Kasir Outlet A
    participant C2 as Kasir Outlet B
    participant C3 as Kasir Outlet C

    Mgr->>CP: Ubah tax_rate dari 10% → 11%
    CP->>API: updateCompanyPOSSettings({ tax_rate: 11 })
    API->>DB: UPDATE CompanyPOSSettings<br/>SET tax_rate = 11
    API-->>CP: { success }
    CP->>BC: post({<br/>  type: 'settings_updated',<br/>  changes: { tax_rate: 11 },<br/>  timestamp: now()<br/>})
    BC-->>C1: Receive settings update
    BC-->>C2: Receive settings update
    BC-->>C3: Receive settings update
    C1->>C1: Apply new tax_rate<br/>untuk transaksi berikutnya
    C2->>C2: Apply new tax_rate
    C3->>C3: Apply new tax_rate
```

***

## Multi-Outlet Monitoring — Fitur Detail

Dashboard Company POS dirancang untuk memantau seluruh outlet dari satu layar. Berikut detail setiap fitur monitoring:

| Fitur | Detail Implementasi | Data Source | Update Frequency |
| - | - | - | - |
| **Revenue Aggregation** | `SUM(total_amount)` dari semua transaksi hari ini, dikelompokkan per outlet | `CompanyPOSTransaction.total_amount` | Real-time via BroadcastChannel |
| **Transaction Counting** | `COUNT(id)` transaksi per outlet, dengan filter status = `completed` | `CompanyPOSTransaction.id` | Real-time |
| **Avg Order Value** | `AVG(total_amount)` per outlet dan global | Computed dari aggregated data | Real-time |
| **Outlet Health Check** | Cek `last_sync` per outlet — hijau (\< 5 min), kuning (5-30 min), merah (> 30 min) | `MAX(CompanyPOSInventory.updated_at)` per outlet | 30s polling |
| **Active Cashier Count** | Hitung sesi kasir aktif per outlet berdasarkan last activity | Active sessions dari BroadcastChannel presence | Real-time |
| **Performance Ranking** | Sort outlet by revenue, transaction count, atau avg order value | Computed dari aggregated data | Real-time |
| **Revenue Trend** | Multi-line chart 7/30/90 hari, satu line per outlet | `CompanyPOSTransaction` grouped by date | On demand |
| **Top Products Cross-Outlet** | Horizontal bar chart produk terlaris di semua outlet, dengan breakdown per outlet | `CompanyPOSTransaction.items` aggregated | On demand |
| **Channel Distribution** | Pie chart proporsi channel (offline, marketplace, whatsapp, dll) per outlet | `CompanyPOSTransaction.sales_channel` | On demand |
| **Stock Alert Badge** | Badge merah di outlet yang memiliki produk di bawah `min_stock` | `CompanyPOSProduct.stock < min_stock` | Via `snishop_inventory_updates` channel |

### Status Badge Logic

| Badge | Warna | Kondisi | Tindakan yang Disarankan |
| - | - | - | - |
| **Online** | Hijau | `last_sync` \< 5 menit lalu | Tidak ada — outlet beroperasi normal |
| **Warning** | Kuning | `last_sync` 5-30 menit lalu | Cek koneksi jaringan outlet |
| **Offline** | Merah | `last_sync` > 30 menit lalu | Investigasi segera — kemungkinan gangguan jaringan atau server |
| **Syncing** | Biru | Sedang ada aktivitas BroadcastChannel | Tunggu hingga sync selesai |

***

## Role-Based Access Control (RBAC)

Akses ke fitur Company POS diatur berdasarkan role user dalam sistem SNISHOP ERP:

| Role | Dashboard View | KPI Metrics | Outlet Detail | Global Settings | Export | Transaction Edit |
| - | - | - | - | - | - | - |
| **Owner** | Full access — semua outlet | Ya | Ya | Full (read + write) | Ya | Ya (void, refund) |
| **Manager** | Full access — semua outlet | Ya | Ya | Read + limited write | Ya | Ya (void only) |
| **Supervisor** | Assigned outlets only | Ya | Ya (assigned) | Read only | Ya (assigned) | No |
| **Cashier** | No access | No | No | No | No | No |
| **Admin (system)** | Full access | Ya | Ya | Full | Ya | Ya |

### RLS Policy Summary

```
Create: company_id = user.active_company_id AND company_id != null
   OR   created_by_id = user.id
   OR   user.role = 'admin'

Read:   (same conditions as Create)

Update: (same conditions as Create)

Delete: (same conditions as Create)
```

***

## Real-Time Sync — Detail Teknis

Sistem sinkronisasi Company POS menggunakan tiga mekanisme berlapis untuk memastikan data selalu terkini:

| Layer | Mekanisme | Latency | Fallback |
| - | - | - | - |
| **Layer 1** | BroadcastChannel API (`snishop_pos_updates`) | \< 100ms | — |
| **Layer 2** | SWR stale-while-revalidate | 1-5s (network) | — |
| **Layer 3** | Polling fallback (30s interval) | 30s | Jika BroadcastChannel tidak tersedia |

### BroadcastChannel Events

| Channel Name | Event Type | Payload | Effect di Dashboard |
| - | - | - | - |
| `snishop_pos_updates` | `transaction_completed` | `{ outlet_id, amount, items_count, timestamp }` | Increment revenue + count, update outlet row |
| `snishop_pos_updates` | `transaction_voided` | `{ outlet_id, amount, transaction_id, timestamp }` | Decrement revenue + count |
| `snishop_pos_updates` | `settings_updated` | `{ changes: {...}, timestamp }` | Refresh settings panel, push ke semua kasir |
| `snishop_inventory_updates` | `stock_changed` | `{ product_id, outlet_id, new_stock, min_stock }` | Update stock alert badge di outlet table |
| `snishop_product_updates` | `price_changed` | `{ product_id, old_price, new_price, timestamp }` | Refresh pricing data di charts |

### Sync Reliability

```mermaid theme={null}
graph TB
    subgraph "Primary: BroadcastChannel"
        BC1[BroadcastChannel API] -->|"< 100ms"| R1[Real-time update]
    end

    subgraph "Secondary: SWR"
        SWR1[SWR Revalidation] -->|"1-5s"| R2[Near real-time]
    end

    subgraph "Tertiary: Polling"
        P1[30s Polling Fallback] -->|"30s"| R3[Eventual consistency]
    end

    BC1 -->|Jika gagal| SWR1
    SWR1 -->|Jika gagal| P1

    style R1 fill:#22c55e,color:#fff
    style R2 fill:#eab308,color:#000
    style R3 fill:#f97316,color:#fff
```

***

## Multi-Channel Sales — Channel Pricing

Setiap produk dapat memiliki harga berbeda per saluran penjualan. Dashboard Company POS mengagregasi data dari semua channel ini:

| Channel | Enum Value | Deskripsi | Kapan Digunakan |
| - | - | - | - |
| **Offline POS** | `offline_pos` | Transaksi di kasir fisik outlet | Default untuk transaksi tatap muka |
| **Offline** | `offline` | Penjualan offline non-POS (legacy) | Backward compatibility |
| **Website** | `website` | Order via website perusahaan | E-commerce web |
| **Online Catalog** | `online_catalog` | Katalog online (non-website) | Katalog digital |
| **Marketplace** | `marketplace` | Tokopedia, Shopee, dll | Order dari platform marketplace |
| **Landing Page** | `landing_page` | Order via landing page kampanye | Kampanye marketing spesifik |
| **WhatsApp** | `whatsapp` | Order via WhatsApp Business | Chat commerce |
| **Reseller** | `reseller` | Order dari reseller/dropshipper | Harga khusus reseller |
| **B2B** | `b2b` | Order korporat/bulk | Harga khusus B2B |
| **Grab** | `grab` | Order via GrabFood | Food delivery |
| **Social Media** | `social_media` | Order via Instagram, TikTok, dll | Social commerce |

***

## Payment Methods — Enum Lengkap

Transaksi POS mendukung berbagai metode pembayaran, termasuk split payment (multi-tender):

| Method | Enum Value | Tipe | Verifikasi | Deskripsi |
| - | - | - | - | - |
| **Tunai** | `cash` | Tunai | Tidak perlu | Pembayaran uang cash, hitung kembalian otomatis |
| **Kartu Debit/Kredit** | `card` | Non-tunai | Auto (EDC) | Via mesin EDC |
| **Transfer Bank** | `transfer` | Non-tunai | Manual | Perlu verifikasi bukti transfer |
| **E-Wallet** | `ewallet` | Non-tunai | Auto | GoPay, OVO, DANA, ShopeePay |
| **QRIS** | `qris` | Non-tunai | Auto | Scan QR universal |
| **Saldo Member** | `saldo` | Internal | Auto | Potong saldo member |
| **Mayar** | `mayar` | Gateway | Auto | Payment gateway Mayar |
| **Hutang** | `debt` | Kredit | Manual | Catat sebagai piutang pelanggan |
| **Manual Transfer** | `manual_transfer` | Non-tunai | Manual | Transfer manual + upload bukti |
| **Midtrans** | `midtrans` | Gateway | Auto | Payment gateway Midtrans |
| **Tripay** | `tripay` | Gateway | Auto | Payment gateway Tripay |
| **Stripe** | `stripe` | Gateway | Auto | Payment gateway Stripe (international) |
| **PayPal** | `paypal` | Gateway | Auto | PayPal (international) |

### Split Payment Flow

```mermaid theme={null}
sequenceDiagram
    participant Kasir as Kasir
    participant POS as POS Cashier
    participant API as Server

    Kasir->>POS: Input total: Rp 500.000
    Kasir->>POS: Tender 1: Cash Rp 200.000
    POS->>POS: remaining = 500.000 - 200.000 = 300.000
    Kasir->>POS: Tender 2: QRIS Rp 300.000
    POS->>POS: remaining = 0 → payment_status = "paid"
    POS->>API: finalizeCompanyPOSCashierSale({<br/>  payments: [<br/>    { method: "cash", amount: 200000 },<br/>    { method: "qris", amount: 300000 }<br/>  ]<br/>})
    API-->>POS: { success, transaction }
```

***

## Product Lifecycle — Data Locking

Produk POS memiliki mekanisme data locking untuk menjaga integritas data setelah ada transaksi yang tercatat:

```mermaid theme={null}
stateDiagram-v2
    [*] --> Active: Produk dibuat<br/>(is_active = true, is_locked = false)

    Active --> Locked: Transaksi pertama<br/>mencatat produk ini<br/>(is_locked = true)
    Locked --> Active: Admin unlock<br/>(requires reason)

    Active --> Deactivated: Soft delete<br/>(is_active = false)
    Locked --> Deactivated: Soft delete<br/>(is_active = false)

    Deactivated --> Active: Reactivate<br/>(is_active = true)

    note right of Locked
        Field yang dikunci (DQ-01):
        - name
        - sku
        - unit
        Perubahan harga tetap boleh
        dengan reason code
    end note

    note right of Deactivated
        deactivated_at & deactivated_by
        dicatat untuk audit trail.
        Histori transaksi tetap intact.
    end note
```

***

## Inventory Movement — Tipe Pergerakan

Setiap perubahan stok tercatat di `CompanyPOSInventory` dengan tipe pergerakan yang berbeda:

| Type | Direction | Trigger | Contoh |
| - | - | - | - |
| `in` | Stok bertambah | Pembelian (PO), Retur pelanggan, Production output | Restock bahan, barang retur dari customer |
| `out` | Stok berkurang | Penjualan POS, Production input, Expired | Transaksi kasir, bahan untuk produksi |
| `adjustment` | Bisa + atau - | Stock opname, Damage, Shrinkage | Koreksi selisih hitung, barang rusak |

### Stock Flow per Transaksi

```mermaid theme={null}
graph LR
    A[Transaksi POS<br/>finalized] --> B[CompanyPOSInventory<br/>type = 'out']
    B --> C[CompanyPOSProduct.stock<br/>-= quantity]
    C --> D{stock < min_stock?}
    D -->|Ya| E[Trigger low stock alert<br/>di dashboard Company POS]
    D -->|Tidak| F[Normal — tidak ada alert]
```

***

## Tips Lanjutan

* **Gunakan fitur perbandingan** untuk mengidentifikasi outlet yang performanya terbaik dan yang perlu perhatian lebih
* **Pastikan semua outlet online** agar data yang tampil selalu akurat dan real-time
* **Manfaatkan laporan konsolidasi** untuk presentasi ke stakeholder tanpa perlu mengumpulkan data manual dari tiap cabang
* **Set alert untuk outlet offline** supaya kamu langsung tahu jika ada outlet yang mengalami masalah koneksi
* **Review avg order value** per outlet untuk mengidentifikasi peluang upselling
* **Cek Last Sync** secara berkala — jika ada outlet dengan sync > 30 menit, investigasi koneksi jaringan
* **Monitor channel distribution** untuk memahami channel penjualan mana yang paling produktif di setiap outlet
* **Gunakan data locking** untuk mencegah perubahan data produk yang sudah memiliki histori transaksi
* **Manfaatkan split payment** untuk mengakomodasi pelanggan yang ingin membayar dengan beberapa metode sekaligus
* **Review payment verification queue** secara berkala untuk memastikan tidak ada pembayaran non-tunai yang tertunda verifikasi


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