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

# Cart

<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: "Cart & Checkout"
description: "Keranjang belanja multi-seller, checkout, payment gateway, POS mirror, voucher & diskon, dan order splitting di SNISHOP ERP."
-------------------------------------------------------------------------------------------------------------------------------------------

# Cart & Checkout

<img src="https://mintcdn.com/quinnofspicy/7mQVU6fsgRvm4pNL/docs/mintlify/screenshots/shop/cart.png?fit=max&auto=format&n=7mQVU6fsgRvm4pNL&q=85&s=4f2f7c5fe64e25ba48026bce6c627d5d" alt="Cart" width="1920" height="1080" data-path="docs/mintlify/screenshots/shop/cart.png" />

## Ringkasan Eksekutif

`Cart.jsx` (837 baris) merupakan pusat sistem keranjang belanja dan checkout SNISHOP ERP yang mendukung arsitektur **multi-seller** — item dari seller berbeda di-group secara otomatis dan diproses sebagai order terpisah. Sistem ini menangani 4 metode pembayaran utama, membuat POS mirror records, broadcast ke order queue via `BroadcastChannel`, dan menghitung commission secara server-side untuk menjamin integritas data finansial.

Checkout flow dirancang untuk menangani kompleksitas marketplace: ketika customer checkout item dari beberapa seller sekaligus, sistem otomatis **split** menjadi beberapa order (`ProductOrder` untuk item official, `MarketplaceOrder` untuk third-party sellers) dengan perhitungan commission yang akurat dan audit trail yang lengkap.

Sistem ini juga mengimplementasikan **security policy** yang ketat: browser-side code tidak pernah menggunakan `asServiceRole`, tidak pernah memanggil `Company.list()`, dan commission rate selalu di-fetch via server function.

### Kapabilitas Utama

| Kapabilitas | Deskripsi |
| - | - |
| **Multi-Seller Cart** | Keranjang mendukung item dari multiple seller dengan grouping otomatis berdasarkan `seller_id` |
| **Order Splitting** | Checkout otomatis split ke `ProductOrder` (official) dan `MarketplaceOrder` (third-party) |
| **4 Metode Pembayaran** | Saldo (wallet), Manual Transfer (offline), Mayar (gateway), SNISHOP Pay (gateway) |
| **POS Mirror** | Setiap order online di-mirror ke `CompanyPOSTransaction` untuk sinkronisasi POS |
| **Real-Time Sync** | BroadcastChannel API untuk sinkronisasi real-time antar tab browser |
| **Voucher & Diskon** | Support voucher code, reseller discount berdasarkan tier membership, dan tip |
| **Quota-Based Products** | Dukungan produk berbasis kuota dengan tracking penggunaan |
| **Commission Engine** | Perhitungan commission marketplace via server function untuk keamanan |
| **Company Context** | Filtering item keranjang berdasarkan konteks company (multi-tenant) |
| **Order History** | Unified order history dari `ProductOrder` dan `MarketplaceOrder` via MyOrders |

## Arsitektur Komponen

```mermaid theme={null}
graph TD
    A[Cart.jsx<br/>837 lines] --> B[Cart Items<br/>7 useState Hooks]
    A --> C[Multi-Seller Grouping<br/>By seller_id]
    A --> D[Order Splitting<br/>ProductOrder vs MarketplaceOrder]
    A --> E[Payment Gateway<br/>4 Methods]
    A --> F[POS Mirror<br/>CompanyPOSTransaction]
    A --> G[BroadcastChannel<br/>Order Queue Sync]
    A --> V[Voucher Engine<br/>ProductVoucher & Discount]
    
    B --> H[filterCartItemsForContext<br/>Company scope]
    
    C --> I[Official Items<br/>seller_id = 'official'<br/>→ ProductOrder]
    C --> J[Third-Party Items<br/>seller_id ≠ 'official'<br/>→ MarketplaceOrder]
    
    E --> K[Saldo<br/>Wallet Balance]
    E --> L[Manual Transfer<br/>Pending Verification]
    E --> M[Mayar<br/>Payment Gateway]
    E --> N[SNISHOP Pay<br/>Static IP Bridge]
    
    F --> O[BroadcastChannel<br/>snishop_order_queue]
    F --> P[BroadcastChannel<br/>snishop_pos_updates]
    
    V --> Q[Voucher Validation<br/>Code & Expiry Check]
    V --> R[Reseller Discount<br/>Tier-Based Calculation]
```

### Diagram Alur Data End-to-End

```mermaid theme={null}
flowchart LR
    subgraph Customer
        A1[Browse Produk] --> A2[Add to Cart]
        A2 --> A3[Review Cart]
        A3 --> A4[Apply Voucher]
        A4 --> A5[Checkout]
        A5 --> A6[Pilih Pembayaran]
        A6 --> A7[Konfirmasi Bayar]
    end

    subgraph Cart_System
        B1[MarketplaceCart<br/>CRUD] --> B2[Group by seller_id]
        B2 --> B3{Seller Type?}
        B3 -->|official| B4[Create ProductOrder]
        B3 -->|third-party| B5[Fetch Commission Rate]
        B5 --> B6[Calculate Commission]
        B6 --> B7[Create MarketplaceOrder]
        B4 --> B8[Create POS Mirror]
        B7 --> B8
        B8 --> B9[BroadcastChannel<br/>Sync]
    end

    subgraph Payment
        C1{Method?}
        C1 -->|Saldo| C2[Deduct Balance]
        C1 -->|Manual| C3[Pending Verification]
        C1 -->|Mayar| C4[Redirect Gateway]
        C1 -->|SNISHOP Pay| C5[Static IP Bridge]
    end

    subgraph Post_Order
        D1[MyOrders<br/>Polling 10s] --> D2[Status Tracking]
        D2 --> D3[Download Result<br/>Digital Products]
    end

    A7 --> B1
    B9 --> C1
    C2 --> D1
    C3 --> D1
    C4 --> D1
    C5 --> D1
```

## Entity & Model

### MarketplaceCart

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `user_id` | UUID | Buyer |
| `company_id` | UUID | Buyer company context |
| `product_id` | UUID | Product reference |
| `product_name` | String | Nama produk (snapshot) |
| `product_price` | Number | Harga saat add to cart |
| `quantity` | Number | Jumlah |
| `metadata` | JSON | Berisi `seller_id`, `product_type` |
| `created_at` | Timestamp | Waktu add to cart |

### ProductOrder (Official Items)

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `product_name` | String | Nama produk |
| `customer_id` | UUID | Buyer |
| `status` | Enum | `pending`, `processing`, `completed`, `cancelled` |
| `subtotal` | Number | Subtotal |
| `discount_amount` | Number | Diskon |
| `total_amount` | Number | Total bayar |
| `payment_method` | Enum | Metode pembayaran |
| `payment_status` | Enum | `unpaid`, `paid`, `refunded` |
| `order_data` | JSON | Detail items |
| `company_id` | UUID | Multi-tenant scoping |
| `created_at` | Timestamp | Waktu order |

### MarketplaceOrder (Third-Party Items)

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `order_number` | String | Nomor order unik |
| `buyer_id` | UUID | Buyer |
| `seller_company_id` | UUID | Seller company |
| `items` | Array | Daftar item yang dipesan |
| `total_amount` | Number | Total amount |
| `commission_amount` | Number | Komisi platform |
| `company_amount` | Number | Amount untuk seller (total - commission) |
| `status` | Enum | `pending`, `processing`, `delivered`, `cancelled` |
| `payment_method` | Enum | Metode pembayaran |
| `payment_status` | Enum | `unpaid`, `paid`, `refunded` |
| `shipping_info` | JSON | Alamat pengiriman |
| `notes` | Text | Catatan order |
| `company_id` | UUID | Buyer company context |
| `created_at` | Timestamp | Waktu order |

### CompanyPOSTransaction (POS Mirror)

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `transaction_number` | String | Nomor transaksi POS |
| `order_id` | UUID | Reference ke order asal |
| `total_amount` | Number | Total amount |
| `payment_method` | Enum | Metode pembayaran |
| `company_id` | UUID | Multi-tenant scoping |
| `created_at` | Timestamp | Waktu transaksi |

## Fitur Utama

### 1. Multi-Seller Checkout

Checkout flow menangani item dari multiple sellers dalam satu transaksi:

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant C as Cart.jsx
    participant DB as Database
    participant SF as Server Function
    participant POS as POS System
    participant BC as BroadcastChannel

    U->>C: Click "Checkout"
    C->>C: Group items by seller_id
    
    loop For each seller group
        alt seller_id == 'official'
            C->>DB: Create ProductOrder
            DB-->>C: Order created
        else Third-party seller
            C->>SF: getMarketplaceCommissionRate()
            SF-->>C: commission_rate (%)
            C->>C: commission = total * rate/100
            C->>C: company_amount = total - commission
            C->>DB: Create MarketplaceOrder
            DB-->>C: Order created
        end
        
        C->>DB: Create CompanyPOSTransaction (mirror)
        C->>BC: Broadcast to 'snishop_order_queue'
        C->>BC: Broadcast to 'snishop_pos_updates'
    end
    
    C->>C: Clear processed cart items
    C->>C: Route to payment method
    C-->>U: Checkout complete
```

### 2. Order Splitting Logic

| Condition | Order Type | Deskripsi |
| - | - | - |
| `seller_id == 'official'` | `ProductOrder` | Item dari SNISHOP official |
| `seller_id != 'official'` | `MarketplaceOrder` | Item dari third-party seller |

**Financial Breakdown (MarketplaceOrder)**:

| Field | Formula | Deskripsi |
| - | - | - |
| `total_amount` | `SUM(item.price * quantity)` | Total amount |
| `commission_amount` | `total_amount * (commission_rate / 100)` | Komisi platform |
| `company_amount` | `total_amount - commission_amount` | Amount untuk seller |

### 3. Payment Methods

| Method | Type | Flow |
| - | - | - |
| **Saldo** | Wallet | Direct deduction dari user balance |
| **Manual Transfer** | Offline | Status = pending, menunggu konfirmasi |
| **Mayar** | Gateway | Redirect ke payment gateway |
| **SNISHOP Pay** | Gateway | Route via static IP bridge server |

**Payment Routing**:

```mermaid theme={null}
flowchart TD
    A[User Select Payment] --> B{Method?}
    
    B -->|Saldo| C{Balance >= Total?}
    C -->|Yes| D[Deduct Balance]
    C -->|No| E[Error: Insufficient Balance]
    D --> F[Update Order: paid]
    
    B -->|Manual Transfer| G[Set Order: pending]
    G --> H[Show Transfer Instructions]
    H --> I[Wait for Admin Confirmation]
    
    B -->|Mayar| J[Create Mayar Payment]
    J --> K[Redirect to Mayar]
    K --> L[Webhook: payment_success]
    
    B -->|SNISHOP Pay| M[Route via Static IP Bridge]
    M --> N[Create Invoice]
    N --> O[Redirect to Payment Page]
    O --> P[Webhook: payment_success]
```

### 4. Company Context Filtering

Cart items di-filter berdasarkan company context menggunakan `filterCartItemsForContext()`:

| Mode | Behavior |
| - | - |
| Personal mode | Hanya tampilkan item dengan seller = 'official' |
| Company mode | Hanya tampilkan item dari company tersebut |

**Security**: Mencegah cross-company cart leakage dalam multi-tenant environment.

### 5. POS Mirror & Broadcast

Setiap order yang dibuat di-cart juga di-mirror ke POS system:

```mermaid theme={null}
flowchart LR
    A[Cart Checkout] --> B[Create Order]
    B --> C[Create CompanyPOSTransaction]
    C --> D[BroadcastChannel<br/>snishop_order_queue]
    C --> E[BroadcastChannel<br/>snishop_pos_updates]
    
    D --> F[POS Order Queue<br/>Real-time update]
    E --> G[POS Dashboard<br/>Transaction sync]
```

**BroadcastChannel API**: Komunikasi real-time antar browser tabs — POS di tab lain bisa langsung receive update tanpa perlu polling.

### 6. Commission Calculation

Commission rate selalu di-fetch dari server function untuk security:

```mermaid theme={null}
sequenceDiagram
    participant C as Cart.jsx
    participant SF as Server Function
    participant DB as Database

    C->>SF: invoke('getMarketplaceCommissionRate')
    SF->>DB: Query MarketplaceCommissionSettings
    DB-->>SF: Settings
    SF-->>C: { commission_rate: 5 }
    C->>C: Calculate commission
    Note over C: Browser never stores<br/>or modifies commission rate
```

**Security Policy**: Browser-side code tidak pernah menghitung commission secara lokal — selalu via server function untuk mencegah manipulation.

### 7. Voucher & Diskon System

Sistem checkout mendukung beberapa mekanisme diskon yang dapat dikombinasikan:

| Mekanisme | Sumber | Deskripsi |
| - | - | - |
| **Voucher Code** | `ProductVoucher` | Kode voucher yang diinput customer saat checkout |
| **Reseller Discount** | `ShopSettings.reseller_discounts` | Diskon otomatis berdasarkan tier membership |
| **Tip Amount** | Customer input | Tip opsional dari customer |
| **Balance Deduction** | `used_balance` | Penggunaan saldo wallet untuk pembayaran |
| **Commission Discount** | `used_commission` | Komisi reseller yang digunakan sebagai potongan |

**Flow Voucher Application**:

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant CART as Cart.jsx
    participant SF as Server Function
    participant DB as Database

    C->>CART: Input voucher code
    CART->>SF: validateVoucher(code, cartTotal)
    SF->>DB: SELECT FROM ProductVoucher WHERE code = ? AND is_active = true
    DB-->>SF: Voucher record
    
    alt Voucher Valid
        SF->>SF: Check: valid_from <= now <= valid_until
        SF->>SF: Check: usage_count < usage_limit
        SF->>SF: Check: cartTotal >= min_purchase
        SF->>SF: Calculate discount
        
        alt discount_type = 'percentage'
            SF->>SF: discount = cartTotal * (discount_value / 100)
            SF->>SF: discount = MIN(discount, max_discount)
        else discount_type = 'fixed_amount'
            SF->>SF: discount = discount_value
        end
        
        SF-->>CART: { valid: true, discount_amount }
        CART->>CART: Apply discount to final_price
    else Voucher Invalid/Expired
        SF-->>CART: { valid: false, reason }
        CART->>C: Show error message
    end
```

**Reseller Discount Tiers** (dari `ShopSettings.reseller_discounts`):

| Tier | Discount | Deskripsi |
| - | - | - |
| `free` | 0% | Tidak ada diskon |
| `pro` | 5% | Diskon tingkat pemula |
| `business` | 10% | Diskon tingkat bisnis |
| `advanced` | 15% | Diskon tingkat lanjutan |
| `enterprise` | 20% | Diskon tingkat enterprise (maksimal) |

**Pricing Calculation Formula**:

```
subtotal         = product_price * quantity
reseller_disc    = subtotal * reseller_discount_rate
voucher_disc     = MIN(subtotal * voucher_pct, max_discount)   -- atau fixed_amount
tip              = customer_input (optional)
final_price      = subtotal - reseller_disc - voucher_disc + tip - used_balance - used_commission
```

### 8. Cart Management

| Action | Deskripsi |
| - | - |
| Update quantity | Increment/decrement item quantity |
| Remove item | Hapus item dari cart |
| Select all/none | Toggle semua item untuk checkout |
| Clear cart | Hapus semua item |
| Save for later | Cart persist di database (bukan localStorage) |

### 9. Order Status Tracking

| Status | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu Pembayaran | Order dibuat, belum bayar |
| `processing` | Sedang Diproses | Pembayaran diterima, sedang diproses |
| `completed` | Selesai | Order selesai/dikirim |
| `cancelled` | Dibatalkan | Order dibatalkan |

### 10. Quota-Based Products

Sistem mendukung produk berbasis kuota yang memungkinkan satu order digunakan berulang kali hingga kuota habis:

| Field | Deskripsi |
| - | - |
| `is_quota_based` | Flag apakah produk menggunakan sistem kuota |
| `quota_total` | Total kuota yang dibeli |
| `quota_used` | Kuota yang sudah digunakan |
| `quota_remaining` | Sisa kuota (`quota_total - quota_used`) |
| `parent_order_id` | Referensi ke order induk (untuk sub-order) |

**Use Case**: Produk digital seperti jasa desain yang bisa direvisi beberapa kali — customer beli 1 order dengan kuota 5 revisi.

## Security Policies

### Browser-Side Restrictions

Cart mengimplementasikan 3 security policy yang di-enforce via test files:

| Policy | File | Rule |
| - | - | - |
| No `asServiceRole` | `cartBrowserPrivilegePolicy.test.js` | Browser code tidak boleh bypass RLS |
| No `Company.list()` | `cartBrowserPrivilegePolicy.test.js` | Browser code tidak boleh enumerate tenants |
| Commission via server | `Cart.commissionScopePolicy.test.js` | Commission rate hanya dari server function |

**Rationale**: Mencegah privilege escalation dan data leakage dalam multi-tenant environment.

### Kebijakan Keamanan RLS (Row-Level Security)

**CompanyPOSTransaction** memiliki RLS paling ketat di antara semua entity cart/order:

```
Kondisi akses (CREATE, READ, UPDATE, DELETE):
  OR:
    1. data.company_id == user.data.active_company_id  DAN  company_id bukan null/kosong
    2. created_by_id == user.id
    3. user.role == 'admin'
```

**Implikasi**:

* Staff POS hanya bisa mengakses transaksi dalam company yang sedang aktif
* User bisa mengakses transaksi yang mereka buat sendiri
* Admin memiliki akses penuh tanpa batasan company
* Mencegah cross-tenant data leakage dalam environment multi-company

**Cart (MarketplaceCart)** menggunakan RLS sederhana tanpa kondisi eksplisit — akses dikontrol melalui `filterCartItemsForContext()` di browser-side untuk filtering berdasarkan company context.

**Commission Rate** tidak pernah diekspos ke browser — selalu di-fetch via server function `getMarketplaceCommissionRate()` untuk mencegah manipulasi nilai komisi dari client-side.

## MyOrders (Order History)

MyOrders.jsx (345 baris) menampilkan unified order history dari 2 sumber:

| Source | Entity | Tag |
| - | - | - |
| Digital products | `ProductOrder` | `source: 'digital'` |
| Marketplace | `MarketplaceOrder` | `source: 'marketplace'` |

**Fitur**:

| Fitur | Deskripsi |
| - | - |
| 10-second polling | Silent auto-refresh untuk update status |
| Order detail modal | Items, order\_data, admin notes, result files |
| Status tracking | pending → processing → completed |
| File download | Download result files untuk order digital |

**Flow MyOrders Polling**:

```mermaid theme={null}
sequenceDiagram
    participant MO as MyOrders.jsx
    participant DB as Database
    participant UI as User Interface

    loop Every 10 seconds
        MO->>DB: SELECT ProductOrder WHERE customer_id = ? ORDER BY created_at DESC
        DB-->>MO: Latest orders
        MO->>DB: SELECT MarketplaceOrder WHERE customer_id = ? ORDER BY created_at DESC
        DB-->>MO: Latest marketplace orders
        MO->>MO: Merge & sort by created_at
        MO->>UI: Re-render order list
        Note over UI: Silent refresh —<br/>no loading spinner
    end
```

## Cara Akses

Dari sidebar, klik menu **Shop** > **Cart**.

URL publik: `/checkout`

## Flow Penggunaan

### Customer

1. Browse produk dari Shop atau Marketplace
2. Klik "Add to Cart" untuk menambahkan produk
3. Buka halaman Cart (`/checkout`)
4. Review item di keranjang
5. Update quantity atau hapus item jika perlu
6. Select items yang ingin di-checkout
7. Klik "Checkout"
8. Pilih metode pembayaran (Saldo / Manual / Mayar / SNISHOP Pay)
9. Isi alamat pengiriman (jika fisik)
10. Selesaikan pembayaran
11. Lihat order status di halaman MyOrders (`/pesanan-saya`)

### Admin

1. Monitor order masuk dari MarketplaceOrdersTab
2. Untuk Manual Transfer: konfirmasi pembayaran dan update status
3. Process order: update status dari pending → processing → completed
4. Upload result files untuk order digital yang selesai
5. Track commission yang masuk dari marketplace orders

## Integrasi Cross-Module

```mermaid theme={null}
graph LR
    A[Cart & Checkout] --> B[Shop<br/>DigitalProduct orders]
    A --> C[Marketplace<br/>Multi-seller orders]
    A --> D[POS<br/>Transaction mirror]
    A --> E[Finance<br/>Payment & commission]
    A --> F[Payment Gateway<br/>Tripay / Mayar]
    A --> G[User<br/>Balance deduction]
    A --> V[Voucher System<br/>ProductVoucher & Discount]
    
    B --> H[ProductOrder]
    C --> I[MarketplaceOrder]
    D --> J[CompanyPOSTransaction]
    E --> K[Commission tracking]
    F --> L[Payment processing]
    G --> M[Saldo wallet]
    V --> N[Price calculation]
```

## Tips

* Test multi-seller checkout dengan item dari official dan third-party seller — pastikan order splitting bekerja dengan benar
* Monitor BroadcastChannel sync — POS di tab lain harus receive update real-time
* Untuk Manual Transfer, set ekspektasi ke customer bahwa order baru diproses setelah pembayaran dikonfirmasi admin
* Commission rate di-fetch via server function untuk security — jangan pernah hardcode atau calculate di browser
* Gunakan company context filtering untuk mencegah cross-company cart leakage
* MyOrders polling (10 detik) memastikan customer lihat update status tanpa perlu refresh manual
* Voucher memiliki `usage_limit` dan `valid_until` — pastikan validasi kedua field ini sebelum apply diskon
* Reseller discount tier dihitung otomatis berdasarkan membership level customer — tidak perlu input manual
* Untuk produk berbasis kuota, monitor `quota_remaining` agar tidak terjadi over-usage

***

## Referensi Entity Schema (base44)

Bagian ini mendokumentasikan schema entity secara lengkap berdasarkan definisi JSONC di `base44/entities/`.

### Diagram Relasi Entity (erDiagram)

```mermaid theme={null}
erDiagram
    MarketplaceCart {
        uuid id PK
        uuid user_id FK
        uuid company_id FK
        uuid product_id FK
        string product_name
        number product_price
        string product_image
        string description
        number quantity
        number subtotal
    }

    DigitalProduct {
        uuid id PK
        string name
        string description
        enum category
        number price
        array images
        boolean is_active
        boolean stock_available
        array order_form_fields
        array variants
        string delivery_time
        array features
        number commission_rate
    }

    ProductOrder {
        uuid id PK
        uuid product_id FK
        string product_name
        string product_category
        number product_price
        number product_commission_rate
        object selected_variant
        uuid customer_id FK
        string customer_email
        string customer_name
        object order_data
        string description
        number used_balance
        number used_commission
        number reseller_discount
        number reseller_discount_amount
        string voucher_code
        string voucher_code_used
        number voucher_discount_amount
        number tip_amount
        number subtotal
        number final_price
        enum status
        string admin_notes
        array result_files
        datetime completed_at
        string processed_by_admin
        string assigned_to_admin
        datetime assigned_at
        number admin_commission_amount
        boolean admin_commission_paid
        number quota_total
        number quota_used
        number quota_remaining
        boolean is_quota_based
        uuid parent_order_id FK
    }

    MarketplaceOrder {
        uuid id PK
        string order_number UK
        uuid customer_id FK
        string customer_email
        string customer_name
        uuid company_id FK
        string company_name
        array items
        number total_amount
        number commission_rate
        number commission_amount
        number company_amount
        enum status
        enum payment_status
        object shipping_address
        string notes
        string tracking_number
        datetime processed_at
        datetime shipped_at
        datetime delivered_at
    }

    MarketplaceCommissionSettings {
        uuid id PK
        number commission_rate
        string description
        number min_withdrawal
        number withdrawal_fee
        boolean is_active
    }

    CompanyPOSTransaction {
        uuid id PK
        uuid company_id FK
        uuid location_id FK
        string location_name
        string transaction_number UK
        string invoice_number
        datetime transaction_date
        array items
        number subtotal
        number discount_amount
        number discount_percentage
        number tax_amount
        number total
        number total_amount
        enum payment_method
        number payment_amount
        array payments
        enum payment_status
        number paid_amount
        number remaining_amount
        number excess_amount
        number change_amount
        string payment_reference
        string payment_verified_by
        datetime payment_verified_at
        uuid customer_id FK
        string customer_name
        string customer_phone
        string customer_address
        number points_earned
        number points_used
        uuid cashier_id FK
        string cashier_name
        uuid assigned_to_id FK
        string assigned_to_name
        string notes
        enum source
        enum order_status
        enum status
        string tracking_number
        string shipping_address
        enum sales_channel
        object metadata
        datetime verified_at
        string verified_by
        datetime processed_at
        datetime shipped_at
        datetime delivered_at
        datetime cancelled_at
    }

    CompanyPOSProduct {
        uuid id PK
        uuid company_id FK
        string name
        string sku UK
        string category
        string category_key
        string category_name
        string variant_key
        string variant_name
        string variant_label
        number size_grams
        string base_product_key
        string description
        number price
        number cost
        number stock
        number min_stock
        string image_url
        array gallery
        array variants
        boolean is_active
        datetime deactivated_at
        string deactivated_by
        number tax_rate
        string supplier
        string unit
        string variant
        string barcode
        enum product_type
        enum packaging_type
        number net_weight_grams
        number gross_weight_grams
        enum storage_condition
        boolean is_bundle
        array bundle_components
        object channel_pricing
        boolean is_locked
        datetime locked_at
        string locked_by
        string lock_reason
        number sold_count
    }

    ShopSettings {
        uuid id PK
        string setting_key UK
        string description
        string banner_image_url
        string banner_title
        string banner_subtitle
        array featured_categories
        object promo_banner
        object reseller_discounts
    }

    ProductVoucher {
        uuid id PK
        string code UK
        string name
        string description
        enum discount_type
        number discount_value
        number min_purchase
        number max_discount
        number usage_limit
        number usage_count
        datetime valid_from
        datetime valid_until
        array applicable_products
        array applicable_categories
        boolean is_active
    }

    Voucher {
        uuid id PK
        string code UK
        string name
        string description
        enum discount_type
        number discount_value
        number min_purchase
        number max_discount
        number usage_limit
        number usage_count
        datetime valid_from
        datetime valid_until
        array applicable_plans
        boolean is_active
        string created_by
    }

    VoucherUsage {
        uuid id PK
        uuid voucher_id FK
        string voucher_code
        uuid user_id FK
        string user_email
        enum plan
        number original_price
        number discount_amount
        number final_price
        string description
        enum billing_period
    }

    DigitalProduct }o--o{ ProductOrder : "product_id → orders"
    MarketplaceCart }o--|| CompanyPOSProduct : "product_id → produk"
    MarketplaceCart }o--|| CompanyPOSTransaction : "checkout → mirror POS"
    ProductOrder }o--|| CompanyPOSProduct : "product_id → produk"
    ProductOrder }o--o| ProductOrder : "parent_order_id → induk"
    ProductOrder }o--o| ProductVoucher : "voucher_code → voucher"
    MarketplaceOrder }o--|| CompanyPOSProduct : "items[] → produk"
    MarketplaceOrder }o--o| MarketplaceCommissionSettings : "commission_rate dari settings"
    CompanyPOSTransaction }o--|| CompanyPOSProduct : "items[] → produk"
    CompanyPOSTransaction }o--|| MarketplaceOrder : "mirror order online"
    CompanyPOSTransaction }o--|| ProductOrder : "mirror order official"
    VoucherUsage }o--|| Voucher : "voucher_id → voucher"
    VoucherUsage }o--|| ProductVoucher : "voucher_id → product voucher"
    ShopSettings ||--o{ ProductOrder : "reseller_discounts → diskon tier"
```

### Tabel Schema Detail Entity

#### MarketplaceCart — Keranjang Belanja Marketplace

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `user_id` | UUID | Ya | — | ID user pemilik keranjang |
| `company_id` | UUID | Ya | — | ID company (konteks multi-tenant) |
| `product_id` | UUID | Ya | — | ID produk yang dimasukkan ke keranjang |
| `product_name` | String | Tidak | — | Nama produk (snapshot saat add to cart) |
| `product_price` | Number | Tidak | — | Harga produk saat add to cart |
| `product_image` | String | Tidak | — | URL gambar produk |
| `description` | String | Tidak | — | Catatan/instruksi khusus dari pembeli (maks 1000 karakter) |
| `quantity` | Number | Ya | `1` | Jumlah item dalam keranjang |
| `subtotal` | Number | Tidak | — | Subtotal (price x quantity) |
| `created_at` | Timestamp | Otomatis | now() | Waktu item ditambahkan ke keranjang |

#### DigitalProduct — Produk Digital (Official)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `name` | String | Ya | — | Nama produk digital |
| `description` | String | Ya | — | Deskripsi produk |
| `category` | Enum | Ya | — | Kategori produk: `zoom`, `design`, `video_editing`, `plagiarism_check`, `other` |
| `price` | Number | Ya | — | Harga produk dasar |
| `images` | Array\[String] | Tidak | — | Array URL gambar produk (rasio 1:1) |
| `is_active` | Boolean | Tidak | `true` | Status aktif produk |
| `stock_available` | Boolean | Tidak | `true` | Ketersediaan stok |
| `order_form_fields` | Array\[Object] | Tidak | — | Form fields yang harus diisi customer saat order |
| `variants` | Array\[Object] | Tidak | — | Varian produk dengan harga berbeda |
| `delivery_time` | String | Tidak | — | Estimasi waktu pengerjaan |
| `features` | Array\[String] | Tidak | — | Fitur-fitur produk |
| `commission_rate` | Number | Tidak | `0` | Persentase komisi untuk admin (0-1) |

**Struktur `order_form_fields[]`**:

| Sub-Field | Tipe | Deskripsi |
| - | - | - |
| `field_id` | String | ID unik field |
| `field_type` | Enum | `text`, `number`, `email`, `file`, `textarea`, `date`, `time`, `select`, `radio`, `checkbox` |
| `field_label` | String | Label field yang ditampilkan |
| `field_placeholder` | String | Placeholder text |
| `is_required` | Boolean | Apakah field wajib diisi (default: `false`) |
| `is_enabled` | Boolean | Apakah field aktif (default: `true`) |
| `options` | Array\[Object] | Opsi untuk type `select`/`radio`/`checkbox` |

**Struktur `variants[]`**:

| Sub-Field | Tipe | Deskripsi |
| - | - | - |
| `name` | String | Nama varian |
| `price` | Number | Harga varian ini |
| `description` | String | Deskripsi varian |
| `is_active` | Boolean | Status aktif varian (default: `true`) |

#### ProductOrder — Pesanan Produk Digital (Official)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `product_id` | UUID | Ya | — | ID produk digital yang dipesan |
| `product_name` | String | Ya | — | Nama produk |
| `product_category` | String | Tidak | — | Kategori produk |
| `product_price` | Number | Ya | — | Harga satuan produk |
| `product_commission_rate` | Number | Tidak | `0` | Commission rate saat order dibuat (snapshot) |
| `selected_variant` | Object | Tidak | — | Varian yang dipilih (jika ada) |
| `customer_id` | UUID | Ya | — | ID customer/pembeli |
| `customer_email` | String | Ya | — | Email customer |
| `customer_name` | String | Tidak | — | Nama customer |
| `order_data` | Object | Tidak | — | Data tambahan order (instruksi khusus, dll) |
| `description` | String | Tidak | — | Catatan/instruksi khusus dari pelanggan (maks 1000 karakter) |
| `used_balance` | Number | Tidak | `0` | Saldo wallet yang digunakan untuk bayar |
| `used_commission` | Number | Tidak | `0` | Komisi reseller yang digunakan sebagai diskon |
| `reseller_discount` | Number | Tidak | `0` | Persentase diskon reseller (0-1) |
| `reseller_discount_amount` | Number | Tidak | `0` | Nominal diskon reseller |
| `voucher_code` | String | Tidak | — | Kode voucher yang digunakan |
| `voucher_code_used` | String | Tidak | — | Kode voucher yang terekam |
| `voucher_discount_amount` | Number | Tidak | `0` | Nominal potongan dari voucher |
| `tip_amount` | Number | Tidak | `0` | Tip dari customer |
| `subtotal` | Number | Tidak | — | Harga sebelum diskon |
| `final_price` | Number | Tidak | — | Harga final setelah semua potongan |
| `status` | Enum | Tidak | `pending` | Status pesanan |
| `admin_notes` | String | Tidak | — | Catatan admin untuk order ini |
| `result_files` | Array\[String] | Tidak | — | Daftar URL file hasil (untuk produk digital) |
| `completed_at` | DateTime | Tidak | — | Waktu order selesai |
| `processed_by_admin` | String | Tidak | — | Email admin yang memproses |
| `assigned_to_admin` | String | Tidak | — | Email admin yang mengambil order |
| `assigned_at` | DateTime | Tidak | — | Waktu order diambil admin |
| `admin_commission_amount` | Number | Tidak | `0` | Komisi admin dari order ini |
| `admin_commission_paid` | Boolean | Tidak | `false` | Status pembayaran komisi admin |
| `quota_total` | Number | Tidak | `0` | Total kuota (untuk produk berbasis kuota) |
| `quota_used` | Number | Tidak | `0` | Kuota yang sudah digunakan |
| `quota_remaining` | Number | Tidak | `0` | Sisa kuota |
| `is_quota_based` | Boolean | Tidak | `false` | Apakah produk berbasis kuota |
| `parent_order_id` | UUID | Tidak | — | Referensi ke order induk (untuk sub-order) |

#### MarketplaceOrder — Pesanan Marketplace (Third-Party Seller)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `order_number` | String | Tidak | — | Nomor order unik (auto-generated) |
| `customer_id` | UUID | Ya | — | ID user yang membeli |
| `customer_email` | String | Tidak | — | Email customer |
| `customer_name` | String | Tidak | — | Nama customer |
| `company_id` | UUID | Ya | — | ID company yang menjual |
| `company_name` | String | Tidak | — | Nama company penjual |
| `items` | Array\[Object] | Ya | — | Daftar produk yang dibeli |
| `total_amount` | Number | Ya | — | Total pembayaran |
| `commission_rate` | Number | Tidak | — | Persentase komisi marketplace (0-100) |
| `commission_amount` | Number | Tidak | — | Jumlah komisi dalam rupiah |
| `company_amount` | Number | Tidak | — | Jumlah yang diterima company setelah komisi |
| `status` | Enum | Tidak | `pending` | Status pesanan |
| `payment_status` | Enum | Tidak | `paid` | Status pembayaran |
| `shipping_address` | Object | Tidak | — | Alamat pengiriman (name, phone, address, city, province, postal\_code) |
| `notes` | String | Tidak | — | Catatan customer untuk order |
| `tracking_number` | String | Tidak | — | Nomor resi pengiriman |
| `processed_at` | DateTime | Tidak | — | Waktu order mulai diproses |
| `shipped_at` | DateTime | Tidak | — | Waktu order dikirim |
| `delivered_at` | DateTime | Tidak | — | Waktu order diterima |

**Struktur `items[]` (MarketplaceOrder)**:

| Sub-Field | Tipe | Deskripsi |
| - | - | - |
| `product_id` | UUID | ID produk |
| `product_name` | String | Nama produk |
| `quantity` | Number | Jumlah yang dipesan |
| `price` | Number | Harga satuan saat order |
| `subtotal` | Number | Subtotal per item (price x quantity) |

**Struktur `shipping_address` (MarketplaceOrder)**:

| Sub-Field | Tipe | Deskripsi |
| - | - | - |
| `name` | String | Nama penerima |
| `phone` | String | Nomor telepon penerima |
| `address` | String | Alamat lengkap |
| `city` | String | Kota |
| `province` | String | Provinsi |
| `postal_code` | String | Kode pos |

#### MarketplaceCommissionSettings — Pengaturan Komisi Marketplace

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `commission_rate` | Number | Ya | `10` | Persentase komisi marketplace (0-100) |
| `description` | String | Tidak | — | Penjelasan kebijakan komisi dan ketentuan penarikan saldo (maks 1000 karakter) |
| `min_withdrawal` | Number | Tidak | `100000` | Minimum penarikan saldo (dalam rupiah) |
| `withdrawal_fee` | Number | Tidak | `5000` | Biaya admin penarikan (dalam rupiah) |
| `is_active` | Boolean | Tidak | `true` | Apakah pengaturan komisi aktif |

#### CompanyPOSTransaction — Transaksi POS (Mirror & Direct)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `company_id` | UUID | Ya | — | ID perusahaan |
| `location_id` | UUID | 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` | DateTime | Tidak | — | Tanggal/waktu transaksi |
| `items` | Array\[Object] | Ya | — | Item yang dibeli |
| `subtotal` | Number | Tidak | — | Subtotal sebelum diskon dan pajak |
| `discount_amount` | Number | Tidak | `0` | Nominal diskon |
| `discount_percentage` | Number | Tidak | `0` | Persentase diskon |
| `tax_amount` | Number | Tidak | `0` | PPN atas transaksi (inclusive/exclusive) |
| `total` | Number | Ya | — | Total akhir |
| `total_amount` | Number | Tidak | — | Total akhir (alias untuk `total`) |
| `payment_method` | Enum | Tidak | — | Metode pembayaran |
| `payment_amount` | Number | Tidak | — | Jumlah uang yang dibayar |
| `payments` | Array\[Object] | Tidak | — | Detail pembayaran multi-tender (split payment) |
| `payment_status` | Enum | Tidak | `pending` | Status pembayaran |
| `paid_amount` | Number | Tidak | `0` | Nominal pembayaran aktual yang telah diverifikasi |
| `remaining_amount` | Number | Tidak | `0` | Sisa kekurangan tagihan |
| `excess_amount` | Number | Tidak | `0` | Kelebihan nominal transfer |
| `change_amount` | Number | Tidak | `0` | Kembalian |
| `payment_reference` | String | Tidak | — | No. referensi bukti transfer/QRIS |
| `payment_verified_by` | String | Tidak | — | Nama pengguna yang memverifikasi pembayaran |
| `payment_verified_at` | DateTime | Tidak | — | Waktu verifikasi pembayaran |
| `customer_id` | UUID | 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 | `0` | Poin loyalty yang didapat |
| `points_used` | Number | Tidak | `0` | Poin loyalty yang dipakai |
| `cashier_id` | UUID | Tidak | — | ID karyawan kasir |
| `cashier_name` | String | Tidak | — | Nama kasir |
| `assigned_to_id` | UUID | Tidak | — | ID staff yang ditugaskan |
| `assigned_to_name` | String | Tidak | — | Nama staff yang ditugaskan |
| `notes` | String | Tidak | — | Catatan transaksi |
| `source` | Enum | Tidak | `pos` | Sumber transaksi |
| `order_status` | Enum | Tidak | `pending` | Status order |
| `status` | Enum | Tidak | `completed` | Status transaksi |
| `tracking_number` | String | Tidak | — | Nomor resi pengiriman |
| `shipping_address` | String | Tidak | — | Alamat pengiriman |
| `sales_channel` | Enum | Tidak | `offline_pos` | Kanal penjualan ternormalisasi |
| `metadata` | Object | Tidak | — | Data tambahan (info order online, dll) |
| `verified_at` | DateTime | Tidak | — | Waktu verifikasi transaksi |
| `verified_by` | String | Tidak | — | User yang memverifikasi |
| `processed_at` | DateTime | Tidak | — | Waktu diproses |
| `shipped_at` | DateTime | Tidak | — | Waktu dikirim |
| `delivered_at` | DateTime | Tidak | — | Waktu diterima |
| `cancelled_at` | DateTime | Tidak | — | Waktu dibatalkan |

**Struktur `items[]` (CompanyPOSTransaction)**:

| Sub-Field | Tipe | Default | Deskripsi |
| - | - | - | - |
| `product_id` | String | — | ID produk |
| `product_name` | String | — | Nama produk |
| `sku` | String | — | SKU produk |
| `quantity` | Number | — | Jumlah |
| `price` | Number | — | Harga satuan |
| `discount` | Number | `0` | Diskon per item |
| `subtotal` | Number | — | Subtotal per item |
| `unit` | String | — | Satuan (pcs, kg, dll) |
| `unit_cost` | Number | — | Harga modal per unit |
| `total_cost` | Number | — | Total modal (unit\_cost x quantity) |
| `note` | String | — | Catatan per-baris dari kasir |

**Struktur `payments[]` (Split Payment)**:

| Sub-Field | Tipe | Default | Deskripsi |
| - | - | - | - |
| `method` | String | — | Metode pembayaran tender ini |
| `amount` | Number | — | Nominal pembayaran tender ini |
| `change_amount` | Number | `0` | Kembalian untuk tender ini |
| `account_id` | String | — | ID akun (untuk transfer) |
| `reference` | String | — | Nomor referensi |
| `verified_by` | String | — | User yang verifikasi tender ini |
| `verified_at` | DateTime | — | Waktu verifikasi |

#### CompanyPOSProduct — Produk POS & Inventory

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `company_id` | UUID | 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 | `0` | Stok saat ini |
| `min_stock` | Number | Tidak | `5` | Minimum stok untuk alert |
| `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 | `true` | Status keaktifan produk |
| `deactivated_at` | DateTime | Tidak | — | Waktu SKU dinonaktifkan (tanpa hapus histori) |
| `deactivated_by` | String | Tidak | — | Akun yang menonaktifkan SKU (audit) |
| `tax_rate` | Number | Tidak | `0` | Persentase pajak (0-100) |
| `supplier` | String | Tidak | — | Nama supplier |
| `unit` | String | Tidak | `pcs` | Satuan produk |
| `variant` | String | Tidak | — | Varian produk (Pedas, Manis, Gurih, dll) |
| `barcode` | String | Tidak | — | Barcode/QR Code produk (EAN-13) |
| `product_type` | Enum | Tidak | `finished_good` | Tipe produk fisik atau paket virtual |
| `packaging_type` | Enum | Tidak | — | Jenis kemasan produk |
| `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 |
| `is_bundle` | Boolean | Tidak | `false` | Menandakan paket bundle virtual |
| `bundle_components` | Array\[Object] | Tidak | — | Komponen SKU penyusun paket bundle |
| `channel_pricing` | Object | Tidak | — | Harga khusus per saluran penjualan |
| `is_locked` | Boolean | Tidak | `false` | Data terkunci karena sudah ada transaksi |
| `locked_at` | DateTime | Tidak | — | Waktu penguncian data |
| `locked_by` | String | Tidak | — | Email user yang mengunci data |
| `lock_reason` | String | Tidak | — | Alasan penguncian |
| `sold_count` | Number | Tidak | `0` | Total jumlah produk terjual |

**Struktur `channel_pricing`**:

| Kanal | Tipe | Deskripsi |
| - | - | - |
| `offline_pos` | Number | Harga untuk POS offline |
| `marketplace` | Number | Harga untuk marketplace |
| `website` | Number | Harga untuk website |
| `whatsapp` | Number | Harga untuk order WhatsApp |
| `reseller` | Number | Harga untuk reseller |
| `b2b` | Number | Harga untuk B2B |
| `grab` | Number | Harga untuk Grab |
| `social_media` | Number | Harga untuk media sosial |

#### ShopSettings — Pengaturan Toko

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `setting_key` | String | Ya | — | Kunci pengaturan unik |
| `description` | String | Tidak | — | Penjelasan konfigurasi toko (maks 1000 karakter) |
| `banner_image_url` | String | Tidak | — | URL banner toko |
| `banner_title` | String | Tidak | — | Judul banner |
| `banner_subtitle` | String | Tidak | — | Subtitle banner |
| `featured_categories` | Array\[String] | Tidak | — | Kategori yang ditampilkan di halaman utama |
| `promo_banner` | Object | Tidak | — | Konfigurasi banner promo |
| `reseller_discounts` | Object | Tidak | — | Diskon per tier membership |

**Struktur `promo_banner`**:

| Sub-Field | Tipe | Deskripsi |
| - | - | - |
| `active` | Boolean | Status aktif banner promo |
| `title` | String | Judul banner promo |
| `description` | String | Deskripsi promo |
| `discount_percentage` | Number | Persentase diskon promo |
| `valid_until` | DateTime | Berlaku hingga tanggal |

**Struktur `reseller_discounts`**:

| Tier | Default | Deskripsi |
| - | - | - |
| `free` | `0` (0%) | Tier gratis — tidak ada diskon |
| `pro` | `0.05` (5%) | Tier pro — diskon 5% |
| `business` | `0.10` (10%) | Tier bisnis — diskon 10% |
| `advanced` | `0.15` (15%) | Tier lanjutan — diskon 15% |
| `enterprise` | `0.20` (20%) | Tier enterprise — diskon 20% |

#### ProductVoucher — Voucher Produk (Checkout)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `code` | String | Ya | — | Kode voucher unik |
| `name` | String | Ya | — | Nama voucher |
| `description` | String | Tidak | — | Penjelasan detail voucher (maks 1000 karakter) |
| `discount_type` | Enum | Ya | — | Jenis potongan: `percentage` atau `fixed_amount` |
| `discount_value` | Number | Ya | — | Nilai discount (% atau Rp) |
| `min_purchase` | Number | Tidak | `0` | Minimum pembelian |
| `max_discount` | Number | Tidak | — | Maksimum potongan (untuk percentage) |
| `usage_limit` | Number | Tidak | — | Batas penggunaan (null = unlimited) |
| `usage_count` | Number | Tidak | `0` | Jumlah sudah dipakai |
| `valid_from` | DateTime | Ya | — | Tanggal mulai aktif |
| `valid_until` | DateTime | Ya | — | Tanggal berakhir |
| `applicable_products` | Array\[String] | Tidak | — | ID produk yang berlaku (kosong = semua) |
| `applicable_categories` | Array\[String] | Tidak | — | Kategori yang berlaku |
| `is_active` | Boolean | Tidak | `true` | Status aktif voucher |

#### Voucher — Voucher Subscription/Plan

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `code` | String | Ya | — | Kode voucher unik |
| `name` | String | Ya | — | Nama voucher |
| `description` | String | Tidak | — | Penjelasan detail voucher (maks 1000 karakter) |
| `discount_type` | Enum | Ya | — | Jenis potongan: `percentage` atau `fixed_amount` |
| `discount_value` | Number | Ya | — | Nilai potongan (persentase 0-100 atau jumlah dalam rupiah) |
| `min_purchase` | Number | Tidak | `0` | Minimum pembelian |
| `max_discount` | Number | Tidak | — | Maksimum potongan (untuk jenis persentase) |
| `usage_limit` | Number | Tidak | — | Batas penggunaan (null = unlimited) |
| `usage_count` | Number | Tidak | `0` | Jumlah voucher sudah digunakan |
| `valid_from` | DateTime | Ya | — | Tanggal mulai aktif |
| `valid_until` | DateTime | Ya | — | Tanggal berakhir |
| `applicable_plans` | Array\[Enum] | Tidak | — | Paket yang bisa menggunakan: `pro`, `business`, `advanced`, `enterprise` |
| `is_active` | Boolean | Tidak | `true` | Status aktif voucher |
| `created_by` | String | Tidak | — | Admin yang membuat voucher |

#### VoucherUsage — Log Penggunaan Voucher

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `id` | UUID | Ya | auto | Primary key |
| `voucher_id` | UUID | Ya | — | ID voucher yang digunakan |
| `voucher_code` | String | Ya | — | Kode voucher |
| `user_id` | UUID | Ya | — | ID user yang menggunakan |
| `user_email` | String | Ya | — | Email user |
| `plan` | Enum | Ya | — | Paket yang dibeli: `pro`, `business`, `advanced`, `enterprise` |
| `original_price` | Number | Ya | — | Harga asli sebelum discount |
| `discount_amount` | Number | Ya | — | Jumlah potongan yang diberikan |
| `final_price` | Number | Ya | — | Harga final setelah discount |
| `description` | String | Tidak | — | Catatan tambahan (maks 1000 karakter) |
| `billing_period` | Enum | Tidak | — | Periode pembayaran: `monthly`, `yearly` |

### Diagram State — Siklus Hidup Order

#### State: ProductOrder (Produk Digital Official)

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Order dibuat dari Cart checkout
    pending --> processing: Pembayaran diterima / admin konfirmasi
    pending --> cancelled: Customer batalkan / timeout pembayaran
    processing --> completed: Admin selesai proses & upload result file
    processing --> cancelled: Admin batalkan proses
    completed --> [*]: Order selesai — file bisa didownload
    cancelled --> [*]: Order dibatalkan — tidak diproses lebih lanjut

    state pending {
        [*] --> menunggu_pembayaran
        menunggu_pembayaran --> menunggu_konfirmasi: Manual Transfer — upload bukti
    }

    state processing {
        [*] --> diambil_admin
        diambil_admin --> dikerjakan
        dikerjakan --> upload_hasil
    }

    state completed {
        [*] --> result_ready
        result_ready --> customer_download
    }
```

#### State: MarketplaceOrder (Pesanan Marketplace Third-Party)

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Order dibuat dari Cart checkout (multi-seller)
    pending --> processing: Pembayaran terverifikasi
    pending --> cancelled: Pembayaran gagal / timeout / dibatalkan
    processing --> shipped: Seller mengirim pesanan + input resi
    processing --> cancelled: Seller/customer batalkan
    shipped --> delivered: Kurir konfirmasi pengiriman
    delivered --> [*]: Order selesai diterima customer
    cancelled --> [*]: Order dibatalkan

    state pending {
        [*] --> menunggu_bayar
        menunggu_bayar --> verifikasi_admin: Manual Transfer
    }

    state processing {
        [*] --> seller_notified
        seller_notified --> packing
        packing --> ready_to_ship
    }

    state shipped {
        [*] --> in_transit
        in_transit --> out_for_delivery
    }
```

#### State: CompanyPOSTransaction (Transaksi POS & Mirror)

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Transaksi dibuat (POS atau mirror dari Cart)
    pending --> processing: Pembayaran mulai diproses
    pending --> completed: Pembayaran tunai langsung selesai
    processing --> paid: Pembayaran diverifikasi (non-tunai)
    processing --> pending_verification: Menunggu verifikasi bukti transfer
    pending_verification --> paid: Admin verifikasi pembayaran
    pending_verification --> rejected: Bukti transfer ditolak
    paid --> shipped: Order fisik mulai dikirim
    shipped --> delivered: Barang diterima customer
    delivered --> completed: Transaksi selesai
    completed --> refunded: Refund/dibatalkan setelah selesai
    pending --> cancelled: Transaksi dibatalkan
    processing --> cancelled: Transaksi dibatalkan saat proses
    rejected --> cancelled: Transaksi dibatalkan setelah ditolak
    refunded --> [*]: Refund selesai
    cancelled --> [*]: Transaksi dibatalkan
    completed --> [*]: Transaksi selesai
```

#### State: ProductVoucher (Siklus Hidup Voucher)

```mermaid theme={null}
stateDiagram-v2
    [*] --> created: Voucher dibuat oleh admin
    created --> active: valid_from tercapai & is_active = true
    created --> inactive: is_active = false
    
    state active {
        [*] --> valid
        valid --> digunakan: Customer apply voucher
        digunakan --> valid: usage_count < usage_limit
        digunakan --> exhausted: usage_count >= usage_limit
    }
    
    active --> expired: valid_until tercapai
    expired --> [*]: Voucher tidak berlaku lagi
    exhausted --> [*]: Kuota penggunaan habis
    inactive --> [*]: Voucher dinonaktifkan
```

### Diagram Sequence — Flow Pembayaran & Verifikasi

#### Flow Add to Cart

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant SHOP as Shop Page
    participant CART as Cart.jsx
    participant DB as Database

    C->>SHOP: Browse produk (DigitalProduct / CompanyPOSProduct)
    SHOP->>SHOP: Tampilkan detail produk, varian, harga
    
    alt Produk Digital
        C->>SHOP: Pilih varian & isi form order
        SHOP->>SHOP: Validate required fields
    else Produk POS/Marketplace
        C->>SHOP: Pilih quantity
    end
    
    C->>CART: Klik "Add to Cart"
    CART->>CART: Snapshot: product_name, product_price, product_image
    CART->>CART: Determine seller_id (official / company_id)
    CART->>DB: INSERT INTO MarketplaceCart (user_id, company_id, product_id, product_name, product_price, quantity, subtotal)
    DB-->>CART: Cart item created
    CART->>CART: Update cart badge count
    CART-->>C: Toast: "Item ditambahkan ke keranjang"
```

#### Flow Checkout Multi-Seller dengan Split Payment

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant CART as Cart.jsx
    participant SF as Server Function
    participant DB as Database
    participant PG as Payment Gateway
    participant POS as POS System
    participant BC as BroadcastChannel

    C->>CART: Pilih item & klik Checkout
    CART->>CART: Group items by seller_id
    
    alt seller_id == 'official'
        CART->>DB: INSERT INTO ProductOrder (status=pending)
        DB-->>CART: ProductOrder created
    else seller_id == third-party
        CART->>SF: getMarketplaceCommissionRate()
        SF->>DB: SELECT FROM MarketplaceCommissionSettings WHERE is_active=true
        DB-->>SF: Settings
        SF-->>CART: { commission_rate }
        CART->>CART: commission_amount = total * rate/100
        CART->>CART: company_amount = total - commission_amount
        CART->>DB: INSERT INTO MarketplaceOrder (status=pending)
        DB-->>CART: MarketplaceOrder created
    end

    CART->>DB: INSERT INTO CompanyPOSTransaction (mirror, source=online)
    CART->>BC: Broadcast('snishop_order_queue', newOrder)
    CART->>BC: Broadcast('snishop_pos_updates', transactionUpdate)

    alt Metode: Saldo
        CART->>SF: deductBalance(userId, totalAmount)
        SF->>DB: UPDATE user_balance SET balance = balance - total
        DB-->>SF: Balance updated
        SF-->>CART: Success
        CART->>DB: UPDATE order SET payment_status='paid'
    else Metode: Manual Transfer
        CART->>DB: UPDATE order SET payment_status='pending'
        CART-->>C: Tampilkan instruksi transfer
        Note over C: Menunggu admin verifikasi
    else Metode: Mayar / SNISHOP Pay
        CART->>PG: Create payment invoice
        PG-->>CART: Payment URL
        CART-->>C: Redirect ke halaman pembayaran
        PG->>SF: Webhook: payment_success
        SF->>DB: UPDATE order SET payment_status='paid'
    end

    CART->>CART: Hapus item yang sudah di-checkout dari cart
    CART-->>C: Checkout selesai — redirect ke MyOrders
```

#### Flow Verifikasi Pembayaran Manual Transfer

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant ADMIN as Admin
    participant DB as Database
    participant BC as BroadcastChannel

    C->>DB: Upload bukti transfer (manual)
    DB-->>C: Bukti berhasil diupload

    Note over ADMIN: Admin melihat order berstatus<br/>payment_status='pending'

    ADMIN->>DB: SELECT orders WHERE payment_status='pending'
    DB-->>ADMIN: Daftar order menunggu verifikasi

    alt Bukti Valid
        ADMIN->>DB: UPDATE order SET payment_status='paid', status='processing'
        DB-->>ADMIN: Order updated
        ADMIN->>BC: Broadcast('snishop_order_queue', orderUpdated)
        BC-->>C: Real-time notifikasi status berubah
    else Bukti Tidak Valid
        ADMIN->>DB: UPDATE order SET payment_status='rejected'
        DB-->>ADMIN: Order updated
        ADMIN-->>C: Bukti transfer ditolak — silakan upload ulang
    end
```

#### Flow Commission Calculation & Payout

```mermaid theme={null}
sequenceDiagram
    participant SF as Server Function
    participant DB as Database
    participant ADMIN as Admin

    Note over SF: Dipicu saat MarketplaceOrder<br/>pembayaran berhasil

    SF->>DB: SELECT MarketplaceCommissionSettings WHERE is_active=true LIMIT 1
    DB-->>SF: { commission_rate: 10, min_withdrawal: 100000, withdrawal_fee: 5000 }

    SF->>SF: commission_amount = total_amount * (commission_rate / 100)
    SF->>SF: company_amount = total_amount - commission_amount

    SF->>DB: UPDATE MarketplaceOrder SET commission_amount, company_amount
    DB-->>SF: Updated

    Note over ADMIN: Admin/Seller bisa ajukan<br/>penarikan saldo company

    ADMIN->>DB: SELECT SUM(company_amount) FROM MarketplaceOrder WHERE company_id=X AND payment_status='paid'
    DB-->>ADMIN: Total saldo company

    alt Saldo >= min_withdrawal
        ADMIN->>SF: requestWithdrawal(companyId, amount)
        SF->>SF: final_amount = amount - withdrawal_fee
        SF->>DB: Create withdrawal request
        DB-->>ADMIN: Penarikan diproses
    else Saldo < min_withdrawal
        SF-->>ADMIN: Error: Saldo belum mencapai minimum penarikan
    end
```

#### Flow Pricing Calculation (Checkout)

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant CART as Cart.jsx
    participant SF as Server Function
    participant DB as Database

    C->>CART: Select items & klik Checkout
    
    CART->>CART: subtotal = SUM(product_price * quantity)
    
    Note over CART: Step 1: Reseller Discount
    
    CART->>DB: GET user membership tier
    DB-->>CART: { tier: 'business' }
    CART->>DB: GET ShopSettings.reseller_discounts
    DB-->>CART: { business: 0.10 }
    CART->>CART: reseller_discount_amount = subtotal * 0.10
    
    Note over CART: Step 2: Voucher
    
    C->>CART: Input voucher code
    CART->>SF: validateVoucher(code, subtotal)
    SF->>DB: SELECT ProductVoucher WHERE code = ? AND is_active = true
    DB-->>SF: Voucher record
    SF->>SF: Validate: expiry, usage_limit, min_purchase
    SF-->>CART: { valid: true, discount_amount }
    
    Note over CART: Step 3: Final Calculation
    
    CART->>CART: final_price = subtotal - reseller_discount_amount - voucher_discount_amount + tip_amount - used_balance - used_commission
    CART->>CART: Validate: final_price >= 0
    
    alt final_price > 0
        CART->>C: Tampilkan total & pilih pembayaran
    else final_price = 0
        CART->>DB: Create order (paid)
        CART-->>C: Order selesai — tanpa pembayaran
    end
```

### Tabel Enum — Nilai yang Diizinkan

#### Enum: ProductOrder.status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu Pembayaran | Order baru dibuat, belum ada pembayaran |
| `processing` | Sedang Diproses | Pembayaran diterima, admin sedang mengerjakan |
| `completed` | Selesai | Order selesai, result file tersedia |
| `cancelled` | Dibatalkan | Order dibatalkan oleh customer atau admin |

#### Enum: MarketplaceOrder.status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu Pembayaran | Order baru dibuat dari checkout multi-seller |
| `processing` | Sedang Diproses | Pembayaran terverifikasi, seller mempersiapkan |
| `shipped` | Dikirim | Seller sudah mengirim + input nomor resi |
| `delivered` | Diterima | Customer konfirmasi barang diterima |
| `cancelled` | Dibatalkan | Order dibatalkan sebelum selesai |

#### Enum: MarketplaceOrder.payment\_status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu Pembayaran | Pembayaran belum diterima |
| `paid` | Dibayar | Pembayaran berhasil diverifikasi |
| `refunded` | Dikembalikan | Dana dikembalikan ke customer |

#### Enum: CompanyPOSTransaction.payment\_method

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `cash` | Tunai | Pembayaran tunai langsung di POS |
| `card` | Kartu | Kartu kredit/debit |
| `transfer` | Transfer Bank | Transfer bank manual |
| `ewallet` | E-Wallet | GoPay, OVO, DANA, dll |
| `qris` | QRIS | Scan QRIS |
| `saldo` | Saldo | Saldo wallet SNISHOP |
| `mayar` | Mayar | Payment gateway Mayar |
| `debt` | Hutang | Transaksi hutang/piutang |
| `manual_transfer` | Transfer Manual | Transfer manual dengan verifikasi admin |
| `midtrans` | Midtrans | Payment gateway Midtrans |
| `tripay` | Tripay | Payment gateway Tripay |
| `stripe` | Stripe | Payment gateway Stripe |
| `paypal` | PayPal | Payment gateway PayPal |

#### Enum: CompanyPOSTransaction.payment\_status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu | Pembayaran belum diproses |
| `pending_verification` | Menunggu Verifikasi | Bukti transfer diupload, menunggu admin |
| `partially_paid` | Dibayar Sebagian | Pembayaran belum lengkap (split payment) |
| `paid` | Dibayar | Pembayaran lunas dan terverifikasi |
| `failed` | Gagal | Pembayaran gagal |
| `refunded` | Dikembalikan | Dana dikembalikan |
| `rejected` | Ditolak | Bukti transfer ditolak admin |

#### Enum: CompanyPOSTransaction.source

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pos` | POS Offline | Transaksi langsung di kasir |
| `online_catalog` | Katalog Online | Order dari katalog online |
| `website` | Website | Order dari website |
| `marketplace` | Marketplace | Order dari marketplace (Tokopedia, Shopee, dll) |
| `landing_page` | Landing Page | Order dari landing page |
| `whatsapp` | WhatsApp | Order via WhatsApp |
| `reseller` | Reseller | Order dari reseller |
| `b2b` | B2B | Order bisnis ke bisnis |
| `grab` | Grab | Order via GrabFood/GrabMart |
| `social_media` | Media Sosial | Order dari media sosial |

#### Enum: CompanyPOSTransaction.sales\_channel

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `offline_pos` | POS Offline | Kanal POS offline (default) |
| `offline` | Offline Lainnya | Kanal offline non-POS |
| `website` | Website | Kanal website |
| `online_catalog` | Katalog Online | Kanal katalog online |
| `marketplace` | Marketplace | Kanal marketplace |
| `landing_page` | Landing Page | Kanal landing page |
| `whatsapp` | WhatsApp | Kanal WhatsApp |
| `reseller` | Reseller | Kanal reseller |
| `b2b` | B2B | Kanal B2B |
| `grab` | Grab | Kanal Grab |
| `social_media` | Media Sosial | Kanal media sosial |

#### Enum: CompanyPOSTransaction.order\_status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu | Order baru masuk |
| `processing` | Diproses | Order sedang diproses |
| `shipped` | Dikirim | Order dalam pengiriman |
| `delivered` | Diterima | Order diterima customer |
| `completed` | Selesai | Order selesai |
| `cancelled` | Dibatalkan | Order dibatalkan |
| `rejected` | Ditolak | Order ditolak |

#### Enum: CompanyPOSTransaction.status

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pending` | Menunggu | Transaksi menunggu |
| `processing` | Diproses | Transaksi sedang diproses |
| `shipped` | Dikirim | Barang dikirim |
| `delivered` | Diterima | Barang diterima |
| `completed` | Selesai | Transaksi selesai |
| `failed` | Gagal | Transaksi gagal |
| `refunded` | Dikembalikan | Transaksi di-refund |
| `cancelled` | Dibatalkan | Transaksi dibatalkan |

#### Enum: CompanyPOSProduct.product\_type

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `finished_good` | Barang Jadi | Produk siap jual (default) |
| `raw_material` | Bahan Baku | Material untuk produksi |
| `semi_finished` | Setengah Jadi | Produk dalam proses produksi |
| `packaging_material` | Bahan Kemasan | Material kemasan (jar, pouch, toples, dll) |
| `bundle` | Paket Bundle | Paket virtual gabungan beberapa SKU |

#### Enum: CompanyPOSProduct.packaging\_type

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `jar_glass` | Toples Kaca | Kemasan toples kaca |
| `pouch_zipper` | Pouch Zipper | Kemasan pouch dengan zipper |
| `pouch_sealed` | Pouch Sealed | Kemasan pouch sealed/press |
| `toples_plastic` | Toples Plastik | Kemasan toples plastik |
| `bottle_plastic` | Botol Plastik | Kemasan botol plastik |
| `bulk` | Curah | Kemasan curah/bulk |
| `other` | Lainnya | Jenis kemasan lainnya |

#### Enum: CompanyPOSProduct.storage\_condition

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `room_temperature` | Suhu Ruang | Penyimpanan suhu ruang (default) |
| `chilled` | Dingin | Penyimpanan di suhu dingin/chiller |
| `frozen` | Beku | Penyimpanan di freezer |

#### Enum: DigitalProduct.category

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `zoom` | Zoom Meeting | Jasa Zoom meeting (webinar, rapat, dll) |
| `design` | Desain Grafis | Jasa desain grafis (logo, banner, dll) |
| `video_editing` | Edit Video | Jasa editing video |
| `plagiarism_check` | Cek Plagiarisme | Jasa pemeriksaan plagiarisme |
| `other` | Lainnya | Produk digital lainnya |

#### Enum: Voucher.discount\_type / ProductVoucher.discount\_type

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `percentage` | Persentase | Potongan berdasarkan persentase (0-100%) |
| `fixed_amount` | Nominal Tetap | Potongan berdasarkan nominal tetap (dalam rupiah) |

#### Enum: VoucherUsage.plan

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `pro` | Pro | Paket Pro |
| `business` | Business | Paket Business |
| `advanced` | Advanced | Paket Advanced |
| `enterprise` | Enterprise | Paket Enterprise |

#### Enum: VoucherUsage.billing\_period

| Nilai | Label (ID) | Deskripsi |
| - | - | - |
| `monthly` | Bulanan | Periode pembayaran bulanan |
| `yearly` | Tahunan | Periode pembayaran tahunan |

### Tabel RBAC — Hak Akses per Role

| Entity | Operasi | Admin | Staff/POS | Customer | Deskripsi Akses |
| - | - | - | - | - | - |
| **MarketplaceCart** | Create | Ya | Ya | Ya (milik sendiri) | Semua user bisa menambah item ke cart |
| **MarketplaceCart** | Read | Ya (semua) | Ya (milik sendiri) | Ya (milik sendiri) | Admin bisa lihat semua cart; user hanya cart sendiri |
| **MarketplaceCart** | Update | Ya | Ya (milik sendiri) | Ya (milik sendiri) | Update quantity, hapus item |
| **MarketplaceCart** | Delete | Ya | Ya (milik sendiri) | Ya (milik sendiri) | Hapus item dari cart |
| **ProductOrder** | Create | Ya | Ya | Ya (checkout sendiri) | Order dibuat saat checkout |
| **ProductOrder** | Read | Ya (semua) | Ya (assigned) | Ya (milik sendiri) | Admin lihat semua; customer hanya order sendiri |
| **ProductOrder** | Update | Ya (status, admin\_notes, result\_files) | Ya (assigned\_at, status) | Tidak | Admin kelola status & upload file; customer tidak bisa update |
| **ProductOrder** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus order |
| **MarketplaceOrder** | Create | Ya | Ya | Ya (checkout sendiri) | Order dibuat saat checkout multi-seller |
| **MarketplaceOrder** | Read | Ya (semua) | Ya (company scope) | Ya (milik sendiri) | Admin lihat semua; seller lihat order di company-nya |
| **MarketplaceOrder** | Update | Ya (semua field) | Ya (status, tracking) | Tidak | Admin kelola penuh; seller update status & resi |
| **MarketplaceOrder** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus |
| **MarketplaceCommissionSettings** | Create | Ya | Tidak | Tidak | Hanya admin yang bisa buat pengaturan komisi |
| **MarketplaceCommissionSettings** | Read | Ya | Ya (read-only) | Tidak | Admin dan staff bisa lihat; customer tidak punya akses |
| **MarketplaceCommissionSettings** | Update | Ya | Tidak | Tidak | Hanya admin yang bisa ubah rate komisi |
| **MarketplaceCommissionSettings** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus |
| **CompanyPOSTransaction** | Create | Ya | Ya (company scope) | Tidak | Admin dan staff POS bisa buat transaksi |
| **CompanyPOSTransaction** | Read | Ya (semua) | Ya (company scope) | Tidak | Admin lihat semua; staff hanya dalam company-nya |
| **CompanyPOSTransaction** | Update | Ya (semua field) | Ya (company scope, terbatas) | Tidak | Admin kelola penuh; staff update terbatas |
| **CompanyPOSTransaction** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus transaksi |
| **CompanyPOSProduct** | Create | Ya | Ya (company scope) | Tidak | Admin dan staff bisa tambah produk |
| **CompanyPOSProduct** | Read | Ya (semua) | Ya (company scope) | Ya (produk aktif saja) | Admin/staff lihat semua; customer hanya produk aktif |
| **CompanyPOSProduct** | Update | Ya (semua field) | Ya (company scope) | Tidak | Admin kelola penuh; staff update terbatas |
| **CompanyPOSProduct** | Delete | Ya (soft delete) | Tidak | Tidak | Hanya admin; produk dinonaktifkan bukan dihapus |
| **ShopSettings** | Create | Ya | Tidak | Tidak | Hanya admin yang bisa buat pengaturan toko |
| **ShopSettings** | Read | Ya | Ya | Ya (public settings) | Semua role bisa baca; customer hanya setting publik |
| **ShopSettings** | Update | Ya | Tidak | Tidak | Hanya admin yang bisa ubah pengaturan toko |
| **ShopSettings** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus |
| **ProductVoucher** | Create | Ya | Tidak | Tidak | Hanya admin yang bisa buat voucher produk |
| **ProductVoucher** | Read | Ya | Ya | Ya (saat checkout) | Admin/staff lihat semua; customer pakai saat checkout |
| **ProductVoucher** | Update | Ya | Tidak | Tidak | Hanya admin yang bisa ubah voucher |
| **ProductVoucher** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus voucher |
| **Voucher** | Create | Ya | Tidak | Tidak | Hanya admin yang bisa buat voucher subscription |
| **Voucher** | Read | Ya | Ya | Tidak | Admin dan staff bisa lihat voucher |
| **Voucher** | Update | Ya | Tidak | Tidak | Hanya admin yang bisa ubah voucher |
| **Voucher** | Delete | Ya | Tidak | Tidak | Hanya admin yang bisa hapus voucher |
| **VoucherUsage** | Create | Otomatis (saat apply voucher) | — | Otomatis | Tercatat otomatis saat customer menggunakan voucher |
| **VoucherUsage** | Read | Ya (semua) | Tidak | Ya (milik sendiri) | Admin lihat semua log; customer hanya log sendiri |
| **DigitalProduct** | Create | Ya | Tidak | Tidak | Hanya admin yang bisa buat produk digital |
| **DigitalProduct** | Read | Ya (semua) | Ya | Ya (produk aktif) | Admin/staff lihat semua; customer hanya produk aktif |
| **DigitalProduct** | Update | Ya | Tidak | Tidak | Hanya admin yang bisa ubah produk digital |
| **DigitalProduct** | Delete | Ya (soft delete) | Tidak | Tidak | Hanya admin; produk dinonaktifkan bukan dihapus |

### Matriks Hubungan Entity

| Entity A | Relasi | Entity B | Kardinalitas | Keterangan |
| - | - | - | - | - |
| MarketplaceCart | product\_id → | CompanyPOSProduct | Many-to-One | Banyak cart item bisa merujuk ke satu produk |
| MarketplaceCart | user\_id → | User | Many-to-One | Banyak cart item milik satu user |
| ProductOrder | product\_id → | DigitalProduct | Many-to-One | Banyak order untuk satu produk digital |
| ProductOrder | parent\_order\_id → | ProductOrder | Self-referencing | Sub-order merujuk ke order induk |
| ProductOrder | voucher\_code → | ProductVoucher | Many-to-One | Banyak order bisa pakai voucher yang sama |
| MarketplaceOrder | company\_id → | Company | Many-to-One | Banyak order dari satu seller company |
| MarketplaceOrder | items\[] → | CompanyPOSProduct | Many-to-Many | Order marketplace berisi banyak produk |
| MarketplaceOrder | commission\_rate ← | MarketplaceCommissionSettings | Many-to-One | Rate komisi dari settings aktif |
| CompanyPOSTransaction | items\[] → | CompanyPOSProduct | Many-to-Many | Transaksi POS berisi banyak produk |
| CompanyPOSTransaction | mirror ← | MarketplaceOrder | One-to-One | Mirror transaksi dari order online |
| CompanyPOSTransaction | mirror ← | ProductOrder | One-to-One | Mirror transaksi dari order official |
| VoucherUsage | voucher\_id → | Voucher | Many-to-One | Banyak penggunaan untuk satu voucher |
| VoucherUsage | voucher\_id → | ProductVoucher | Many-to-One | Banyak penggunaan untuk satu product voucher |
| ShopSettings | reseller\_discounts → | ProductOrder | One-to-Many | Settings diskon berlaku untuk banyak order |

### Penanganan Error & Edge Cases

| Skenario | Penanganan | Deskripsi |
| - | - | - |
| Saldo tidak cukup | Error message | Tampilkan "Saldo tidak mencukupi" dan arahkan ke top-up |
| Voucher expired | Reject voucher | Voucher ditolak, customer harus input kode lain |
| Voucher usage\_limit habis | Reject voucher | Voucher sudah mencapai batas penggunaan |
| Produk nonaktif saat checkout | Remove from cart | Item dihapus otomatis, notifikasi ke customer |
| Stok habis (POS Product) | Disable add to cart | Tombol "Add to Cart" dinonaktifkan saat stock = 0 |
| Commission settings tidak aktif | Fallback rate = 0 | Jika tidak ada settings aktif, commission = 0 |
| BroadcastChannel tidak tersedia | Fallback ke polling | Jika browser tidak support BroadcastChannel |
| Payment gateway timeout | Retry / cancel | Order tetap pending, customer bisa retry pembayaran |
| Cross-company cart leak | Filter by context | `filterCartItemsForContext()` mencegah kebocoran data |
| Duplicate order (double-click) | Idempotency check | Cegah pembuatan order duplikat |
| Quota habis (quota-based) | Block usage | Tidak bisa gunakan order jika `quota_remaining = 0` |


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