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

# Toko

<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: "Marketplace (Multi-Seller Storefront)"
description: "Marketplace multi-seller Tokopedia-style: katalog gabungan, stock aggregation, banner CMS, dan Shopee sales sync di SNISHOP ERP."
-----------------------------------------------------------------------------------------------------------------------------------------------

# Marketplace (Multi-Seller Storefront)

<img src="https://mintcdn.com/quinnofspicy/ny1xnfpEa_OBdv6T/docs/mintlify/screenshots/shop/toko.png?fit=max&auto=format&n=ny1xnfpEa_OBdv6T&q=85&s=56f467d2822bb98032f3a1e183ef8843" alt="Toko" width="1920" height="1080" data-path="docs/mintlify/screenshots/shop/toko.png" />

Marketplace adalah storefront publik bergaya Tokopedia yang mendukung multi-seller — produk dari berbagai company/tenant ditampilkan dalam satu katalog terpadu. Marketplace.jsx (812 baris) menggabungkan produk fisik (`CompanyPOSProduct`) dan produk digital (`DigitalProduct`) menjadi satu grid produk yang bisa di-filter, di-sort, dan di-search.

Sistem ini menggunakan inventory-aware stock aggregation yang mengambil data stok langsung dari modul Inventory, memastikan angka stok yang ditampilkan akurat. Fitur "For You" menggunakan scoring algorithm yang menggabungkan popularitas (sold\_count) dan recency (created\_date) untuk ranking produk.

Marketplace juga dilengkapi banner carousel dengan auto-rotate, category grid, trust badges, dan progressive loading (20 produk per batch). Admin bisa mengelola banner, commission rate, dan display settings melalui MarketplaceSettingsTab.

## Arsitektur Komponen

```mermaid theme={null}
graph TD
    A[Marketplace.jsx<br/>812 lines] --> B[Combined Catalog<br/>CompanyPOSProduct + DigitalProduct]
    A --> C[Banner Carousel<br/>Auto-rotate 5s]
    A --> D[Category Grid<br/>Icon + Label]
    A --> E[Product Tabs<br/>For You / Terbaru / Terlaris]
    A --> F[MarketplaceProductDetail.jsx<br/>358 lines]
    A --> G[MarketplaceShowcase.jsx<br/>184 lines]
    
    B --> H[Inventory Stock<br/>Aggregation]
    B --> I[Seller Resolution<br/>Company profiles]
    
    E --> J[For You Scoring<br/>sold_count * 0.5 + recency]
    
    K[MarketplaceSettingsTab.jsx<br/>320 lines] --> L[Commission Settings]
    K --> M[Banner CMS]
    K --> N[Display Config]
```

## Entity & Model

### CompanyPOSProduct (Physical Products)

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `name` | String | Nama produk |
| `price` | Number | Harga jual (Rp) |
| `category` | String | Kategori produk |
| `description` | Text | Deskripsi produk |
| `images` | Array | URL gambar produk |
| `is_active` | Boolean | Status aktif |
| `stock` | Number | Stok dasar |
| `rating` | Number | Rating produk (1-5) |
| `sold_count` | Number | Jumlah terjual |
| `company_id` | UUID | Seller company |
| `created_at` | Timestamp | Waktu pembuatan |

### DigitalProduct (Digital Products)

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `name` | String | Nama produk digital |
| `price` | Number | Harga jual (Rp) |
| `category` | Enum | `zoom`, `design`, `plagiarism_check`, `streaming`, `video_editing`, `other` |
| `images` | Array | URL gambar |
| `company_id` | UUID | Seller company |
| `is_active` | Boolean | Status aktif |

### MarketplaceCart

| Field | Tipe | Deskripsi |
| - | - | - |
| `id` | UUID | Primary key |
| `user_id` | UUID | Buyer |
| `company_id` | UUID | Buyer company context |
| `product_id` | UUID | Produk yang ditambahkan |
| `quantity` | Number | Jumlah |
| `metadata` | JSON | Berisi `seller_id` untuk grouping |
| `created_at` | Timestamp | Waktu add to cart |

### MarketplaceCommissionSettings

| Field | Tipe | Deskripsi |
| - | - | - |
| `commission_rate` | Number | Persentase komisi platform (%) |
| `min_withdrawal` | Number | Minimum penarikan untuk seller |
| `withdrawal_fee` | Number | Biaya penarikan (%) |
| `banners` | Array | Daftar banner aktif |
| `display_settings` | JSON | Konfigurasi tampilan |
| `company_id` | UUID | Multi-tenant scoping |

## Fitur Utama

### 1. Combined Product Catalog

Marketplace menggabungkan 2 sumber produk menjadi satu grid:

| Source | Entity | Tipe |
| - | - | - |
| Physical products | `CompanyPOSProduct` | Produk fisik (saos, bumbu, dll) |
| Digital products | `DigitalProduct` | Produk digital (Zoom, Canva, dll) |

**Merge Logic**:

```mermaid theme={null}
flowchart TD
    A[Fetch CompanyPOSProduct] --> C[Merge Arrays]
    B[Fetch DigitalProduct] --> C
    C --> D[Filter: is_active = true]
    D --> E[Apply Search Query]
    E --> F[Apply Category Filter]
    F --> G[Apply Sort Algorithm]
    G --> H[Display Product Grid]
```

### 2. Inventory-Aware Stock Aggregation

Stok produk fisik tidak hanya mengambil dari `CompanyPOSProduct.stock`, tapi di-aggregate dari modul Inventory:

```mermaid theme={null}
sequenceDiagram
    participant M as Marketplace.jsx
    participant CP as CompanyPOSProduct
    participant INV as Inventory
    participant UI as Product Card

    M->>CP: Fetch all active products
    CP-->>M: Product list
    M->>INV: aggregateAvailableStockByProduct()
    INV-->>M: Stock per product_id
    M->>M: Merge stock into product data
    M->>UI: Render with accurate stock
```

**Function**: `aggregateAvailableStockByProduct()` menghitung total stok tersedia dari semua warehouse locations untuk setiap produk.

### 3. "For You" Product Ranking

Tab "For You" menggunakan scoring algorithm yang menggabungkan popularitas dan recency:

```
score = (sold_count * 0.5) + (created_date_timestamp / 1e12)
```

| Komponen | Bobot | Deskripsi |
| - | - | - |
| `sold_count * 0.5` | Popularitas | Produk terlaris mendapat skor lebih tinggi |
| `created_date / 1e12` | Recency | Produk baru mendapat bonus skor |

**Result**: Produk yang populer dan baru muncul mendapat ranking tertinggi.

### 4. Product Tabs

| Tab | Sort Logic | Deskripsi |
| - | - | - |
| **For You** | Score = (sold \* 0.5) + recency | Hybrid ranking |
| **Terbaru** | `created_at` DESC | Produk terbaru dulu |
| **Terlaris** | `sold_count` DESC | Best sellers |

### 5. Banner Carousel

Banner carousel dengan auto-rotate setiap 5 detik:

| Fitur | Deskripsi |
| - | - |
| Auto-rotate | 5 detik per banner |
| Manual navigation | Dot indicator bisa diklik |
| Click-through | Banner bisa di-link ke URL tertentu |
| Admin CMS | Upload dan kelola banner via MarketplaceSettingsTab |

### 6. Category Grid

Grid kategori dengan icon dan label untuk quick filter:

| Kategori | Icon | Deskripsi |
| - | - | - |
| Makanan | 🍜 | Produk makanan olahan |
| Minuman | 🥤 | Produk minuman |
| Bumbu & Saos | 🌶️ | Saos dan bumbu masak |
| Snack | 🍿 | Cemilan dan snack |
| Digital | 💻 | Produk digital |
| Lainnya | 📦 | Kategori lain |

### 7. Seller Resolution

Marketplace hanya fetch company yang benar-benar memiliki produk aktif — bukan enumerate semua tenants:

```mermaid theme={null}
flowchart TD
    A[Fetch Products] --> B[Extract Unique company_ids]
    B --> C[Fetch Companies by IDs]
    C --> D[Map company_id → company_name]
    D --> E[Display Seller Info on Product Card]
```

**Security Boundary**: Tidak memanggil `Company.list()` yang bisa expose semua tenants.

### 8. Progressive Loading

Product grid menggunakan progressive loading untuk performa:

| Parameter | Nilai | Deskripsi |
| - | - | - |
| Initial show | 20 products | Batch pertama |
| Load more | +20 per click | Tombol "Load More" |
| State | `showCount` | Incremental counter |

### 9. Trust Badges

Marketplace menampilkan trust badges untuk membangun kepercayaan:

| Badge | Deskripsi |
| - | - |
| 🛡️ 100% Original | Jaminan keaslian produk |
| 🚚 Fast Shipping | Pengiriman cepat |
| 💬 24/7 Support | Customer support |
| ⭐ Top Rated | Rating tinggi |

### 10. Demo Mode

Marketplace mendukung demo mode untuk preview tanpa data real:

| Condition | Behavior |
| - | - |
| `isDemoMode()` = true | Gunakan `DEMO_PRODUCTS` dan `DEMO_COMPANY` |
| `isDemoMode()` = false | Fetch dari base44 entities |

## Admin Panel

### MarketplaceSettingsTab (320 lines)

3-section admin panel:

| Section | Fitur |
| - | - |
| **Commission** | Atur commission rate (%), min withdrawal, withdrawal fee |
| **Banners** | Upload banner images (Cloudinary), set link URL, reorder |
| **Display** | Konfigurasi tampilan (show/hide sections) |

**Commission Live Example**:

```
Jika commission_rate = 5% dan produk dijual Rp 100.000:
- Platform commission = Rp 5.000
- Seller receives = Rp 95.000
```

### MarketplaceOrdersTab (188 lines)

Dashboard order marketplace:

| Fitur | Deskripsi |
| - | - |
| Stats Dashboard | Total orders, revenue, commission |
| Search | By order number, customer, company |
| Tab Filter | All / Pending / Delivered |
| Order Detail | Items, amounts, status |

## Cara Akses

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

URL publik: `/marketplace`

## Flow Penggunaan

### Customer / Visitor

1. Akses `/marketplace` dari browser
2. Browse banner carousel untuk promo terbaru
3. Filter by kategori atau gunakan search
4. Pilih tab: For You / Terbaru / Terlaris
5. Klik produk untuk melihat detail
6. Lihat info seller dan stock availability
7. Pilih quantity dan klik "Add to Cart"
8. Lanjut ke checkout dari cart page

### Admin

1. Buka Marketplace dari sidebar untuk preview
2. Kelola banner melalui MarketplaceSettingsTab
3. Atur commission rate dan withdrawal settings
4. Monitor orders melalui MarketplaceOrdersTab
5. Review statistik: total orders, revenue, commission

## Integrasi Cross-Module

```mermaid theme={null}
graph LR
    A[Marketplace] --> B[Inventory<br/>Stock aggregation]
    A --> C[POS<br/>CompanyPOSProduct]
    A --> D[Shop<br/>DigitalProduct]
    A --> E[Cart<br/>Add to cart]
    A --> F[CRM<br/>Company profiles]
    A --> G[Cloudinary<br/>Banner images]
    
    B --> H[aggregateAvailableStockByProduct]
    C --> I[Physical products]
    D --> J[Digital products]
    E --> K[Checkout flow]
    F --> L[Seller resolution]
```

## Shopee Sales Sync

Marketplace.jsx terintegrasi dengan `shopeeSalesSync.js` (187 baris) untuk menampilkan angka penjualan yang menggabungkan:

| Source | Deskripsi |
| - | - |
| `shopee_mall_sales` | Angka statis dari Shopee |
| Organic counter | localStorage auto-increment (1 per 15-45 detik) |
| System sales | Actual sales dari ERP |

**Weighted Distribution**: Angka penjualan didistribusikan ke produk berdasarkan popularitas.

## Tips

* Gunakan foto produk yang konsisten dan profesional untuk membangun brand trust
* Monitor stock aggregation — jika ada discrepancy antara Marketplace dan Inventory, check warehouse data
* Atur commission rate yang fair untuk seller — terlalu tinggi mengurangi minat seller bergabung
* Gunakan banner carousel untuk highlight promo atau produk baru
* Progressive loading (20 per batch) menjaga performa — jangan load semua produk sekaligus
* For You scoring algorithm otomatis mempromosikan produk populer dan baru — tidak perlu manual sorting

***

## Referensi Entity Schema (base44)

Berikut adalah dokumentasi schema lengkap untuk setiap entity yang digunakan dalam modul Toko/Marketplace, diambil langsung dari definisi JSONC di `base44/entities/`.

### Diagram Relasi Entity (erDiagram)

```mermaid theme={null}
erDiagram
    Customer ||--o{ MarketplaceOrder : "membuat pesanan"
    Customer ||--o{ MarketplaceCart : "menambahkan ke keranjang"
    Customer ||--o{ ProductOrder : "memesan produk digital"
    Customer }o--o| CustomerMembership : "memiliki level"

    MarketplaceOrder }o--|| CompanyPOSProduct : "berisi produk fisik"
    MarketplaceOrder }o--|| DigitalProduct : "berisi produk digital"

    MarketplaceCart }o--|| CompanyPOSProduct : "menyimpan produk fisik"
    MarketplaceCart }o--|| DigitalProduct : "menyimpan produk digital"

    CompanyPOSProduct }o--|| CompanyPOSCategory : "dikategorikan oleh"
    CompanyPOSProduct }o--o{ CompanyPOSInventory : "memiliki riwayat stok"
    CompanyPOSInventory }o--|| WarehouseLocation : "berlokasi di"

    DigitalProduct }o--|| CompanyPOSCategory : "dikategorikan oleh"

    ProductOrder }o--|| DigitalProduct : "merujuk ke"

    ShopSettings ||--|| CustomerMembership : "mengatur diskon reseller"

    MarketplaceCommissionSettings ||--o{ MarketplaceOrder : "menentukan komisi"

    Customer {
        uuid id PK
        string company_id FK
        string name
        string email
        string phone
        string customer_type
        string membership_level_id FK
        string status
        number lifetime_value
        number total_orders
    }

    CustomerMembership {
        uuid id PK
        string company_id FK
        string level_name
        string level_key
        number discount_percentage
        number points_multiplier
        string scheme_type
    }

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

    MarketplaceCart {
        uuid id PK
        string user_id FK
        string company_id FK
        string product_id FK
        string product_name
        number product_price
        string product_image
        string description
        number quantity
        number subtotal
    }

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

    MarketplaceOrder {
        uuid id PK
        string order_number
        string customer_id FK
        string customer_email
        string customer_name
        string company_id FK
        string company_name
        array items
        number total_amount
        number commission_rate
        number commission_amount
        number company_amount
        string status
        string payment_status
        object shipping_address
        string tracking_number
    }

    ProductOrder {
        uuid id PK
        string product_id FK
        string product_name
        string product_category
        number product_price
        number product_commission_rate
        string customer_id FK
        string customer_email
        string customer_name
        object order_data
        number subtotal
        number final_price
        string status
    }

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

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

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

    CompanyPOSSettings {
        uuid id PK
        string company_id FK
        object receipt_settings
    }

    WarehouseLocation {
        uuid id PK
        string company_id FK
        string location_name
        string location_code
        string location_type
        string description
        string address
        string city
        number capacity
        number current_utilization
        boolean is_active
    }
```

### Tabel Schema Entity Lengkap

#### DigitalProduct

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `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 (Rp) |
| `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 (produk digital bisa unlimited) |
| `order_form_fields` | Array\[Object] | Tidak | — | Form fields yang harus diisi customer saat memesan |
| `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[*]`:**

| 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 tampilan field |
| `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] | Pilihan untuk `select`/`radio`/`checkbox` |

**Struktur `variants[*]`:**

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

#### MarketplaceCart

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `user_id` | String (UUID) | Ya | — | ID user pemilik keranjang |
| `company_id` | String (UUID) | Ya | — | ID company (konteks pembeli) |
| `product_id` | String (UUID) | Ya | — | ID produk yang ditambahkan |
| `product_name` | String | Tidak | — | Nama produk (denormalisasi) |
| `product_price` | Number | Tidak | — | Harga satuan produk (denormalisasi) |
| `product_image` | String | Tidak | — | URL gambar produk (denormalisasi) |
| `description` | String | Tidak | — | Catatan/instruksi khusus dari pembeli (maks 1000 karakter) |
| `quantity` | Number | Tidak | `1` | Jumlah item di keranjang |
| `subtotal` | Number | Tidak | — | Subtotal (price x quantity) |

#### MarketplaceCommissionSettings

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

#### MarketplaceOrder

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `order_number` | String | Tidak | — | Nomor order unik |
| `customer_id` | String (UUID) | Ya | — | ID user yang membeli |
| `customer_email` | String | Tidak | — | Email customer |
| `customer_name` | String | Tidak | — | Nama customer |
| `company_id` | String (UUID) | Ya | — | ID company yang menjual |
| `company_name` | String | Tidak | — | Nama company penjual |
| `items` | Array\[Object] | Ya | — | List produk yang dibeli |
| `total_amount` | Number | Ya | — | Total pembayaran (Rp) |
| `commission_rate` | Number | Tidak | — | Persentase komisi saat order (0-100) |
| `commission_amount` | Number | Tidak | — | Jumlah komisi dalam rupiah |
| `company_amount` | Number | Tidak | — | Jumlah diterima company setelah komisi |
| `status` | Enum | Tidak | `pending` | Status pesanan |
| `payment_status` | Enum | Tidak | `paid` | Status pembayaran |
| `shipping_address` | Object | Tidak | — | Alamat pengiriman lengkap |
| `notes` | String | Tidak | — | Catatan customer |
| `tracking_number` | String | Tidak | — | Nomor resi pengiriman |
| `processed_at` | DateTime | Tidak | — | Waktu order diproses |
| `shipped_at` | DateTime | Tidak | — | Waktu order dikirim |
| `delivered_at` | DateTime | Tidak | — | Waktu order diterima |

**Struktur `items[*]`:**

| Field | Tipe | Deskripsi |
| - | - | - |
| `product_id` | String | ID produk yang dibeli |
| `product_name` | String | Nama produk |
| `quantity` | Number | Jumlah yang dibeli |
| `price` | Number | Harga satuan |
| `subtotal` | Number | Subtotal per item |

**Struktur `shipping_address`:**

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

#### ProductOrder (Pesanan Produk Digital)

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `product_id` | String (UUID) | Ya | — | ID produk digital yang dipesan |
| `product_name` | String | Ya | — | Nama produk |
| `product_category` | String | Tidak | — | Kategori produk |
| `product_price` | Number | Ya | — | Harga dasar produk |
| `product_commission_rate` | Number | Tidak | `0` | Commission rate saat order dibuat |
| `selected_variant` | Object | Tidak | — | Varian yang dipilih |
| `customer_id` | String (UUID) | Ya | — | ID customer pemesan |
| `customer_email` | String | Ya | — | Email customer |
| `customer_name` | String | Tidak | — | Nama customer |
| `order_data` | Object | Tidak | — | Data form order (jawaban form fields) |
| `description` | String | Tidak | — | Catatan dari pelanggan (maks 1000 karakter) |
| `used_balance` | Number | Tidak | `0` | Saldo yang digunakan |
| `used_commission` | Number | Tidak | `0` | Komisi yang digunakan |
| `reseller_discount` | Number | Tidak | `0` | Persentase diskon reseller |
| `reseller_discount_amount` | Number | Tidak | `0` | Nominal diskon reseller |
| `voucher_code` | String | Tidak | — | Kode voucher yang digunakan |
| `voucher_discount_amount` | Number | Tidak | `0` | Potongan dari voucher |
| `tip_amount` | Number | Tidak | `0` | Tip dari customer |
| `subtotal` | Number | Tidak | — | Harga sebelum discount |
| `final_price` | Number | Tidak | — | Harga final setelah semua potongan |
| `status` | Enum | Tidak | `pending` | Status pesanan |
| `admin_notes` | String | Tidak | — | Catatan admin |
| `result_files` | Array\[String] | Tidak | — | URL file hasil pengerjaan |
| `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 dalam rupiah |
| `admin_commission_paid` | Boolean | Tidak | `false` | Status pembayaran komisi admin |
| `quota_total` | Number | Tidak | `0` | Total kuota (jika berbasis kuota) |
| `quota_used` | Number | Tidak | `0` | Kuota yang sudah terpakai |
| `quota_remaining` | Number | Tidak | `0` | Sisa kuota |
| `is_quota_based` | Boolean | Tidak | `false` | Apakah order berbasis kuota |
| `parent_order_id` | String | Tidak | — | ID order induk (untuk sub-order) |

#### ShopSettings

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `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 aktif |
| `reseller_discounts` | Object | Tidak | — | Diskon persentase per tier membership |

**Struktur `promo_banner`:**

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

**Struktur `reseller_discounts`:**

| Tier | Default | Deskripsi |
| - | - | - |
| `free` | `0` | Diskon untuk tier gratis (0%) |
| `pro` | `0.05` | Diskon untuk tier Pro (5%) |
| `business` | `0.10` | Diskon untuk tier Business (10%) |
| `advanced` | `0.15` | Diskon untuk tier Advanced (15%) |
| `enterprise` | `0.20` | Diskon untuk tier Enterprise (20%) |

#### CompanyPOSCategory

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | String (UUID) | Ya | — | ID perusahaan pemilik kategori |
| `name` | String | Ya | — | Nama kategori |
| `description` | String | Tidak | — | Penjelasan jenis produk dalam kategori (maks 1000 karakter) |
| `icon` | String | Tidak | — | Icon emoji untuk kategori |
| `color` | String | Tidak | `#3b82f6` | Warna tampilan kategori |
| `order` | Number | Tidak | `0` | Urutan pengurutan kategori |

#### CompanyPOSInventory

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | String (UUID) | Ya | — | ID perusahaan |
| `product_id` | String (UUID) | Ya | — | ID produk |
| `product_name` | String | Tidak | — | Nama produk (denormalisasi) |
| `type` | Enum | Ya | — | Tipe pergerakan stok: `in`, `out`, `adjustment` |
| `quantity` | Number | Ya | — | Jumlah perubahan stok |
| `stock_before` | Number | Tidak | — | Stok sebelum perubahan |
| `stock_after` | Number | Tidak | — | Stok setelah perubahan |
| `reason` | String | Tidak | — | Alasan (pembelian, penjualan, rusak, dll) |
| `reference_id` | String | Tidak | — | ID transaksi terkait |
| `notes` | String | Tidak | — | Catatan tambahan |
| `performed_by` | String | Tidak | — | User yang melakukan perubahan |

#### CompanyPOSSettings

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | String (UUID) | Ya | — | ID perusahaan |
| `description` | String | Tidak | — | Catatan mengenai konfigurasi POS perusahaan (maks 1000 karakter) |
| `receipt_settings` | Object | Tidak | — | Pengaturan struk/receipt |

**Struktur `receipt_settings`:**

| Field | Tipe | Default | Deskripsi |
| - | - | - | - |
| `logo_url` | String | — | URL logo perusahaan untuk struk |
| `business_name` | String | — | Nama bisnis di struk |
| `business_address` | String | — | Alamat lengkap |
| `business_phone` | String | — | Nomor telepon |
| `business_email` | String | — | Email bisnis |
| `tax_id` | String | — | NPWP/Tax ID |
| `receipt_width` | Enum | `80mm` | Ukuran kertas struk: `58mm`, `80mm` |
| `show_logo` | Boolean | `true` | Tampilkan logo di struk |
| `show_tax_id` | Boolean | `false` | Tampilkan NPWP di struk |
| `show_cashier_name` | Boolean | `true` | Tampilkan nama kasir |
| `show_customer_info` | Boolean | `true` | Tampilkan info customer |
| `show_item_image` | Boolean | `false` | Tampilkan gambar item |
| `header_text` | String | `STRUK PEMBELIAN` | Teks header struk |
| `footer_message` | String | `Terima kasih...` | Pesan footer struk |
| `print_auto_after_transaction` | Boolean | `true` | Cetak otomatis setelah transaksi |
| `font_size` | Enum | `medium` | Ukuran font: `small`, `medium`, `large` |

#### Customer

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | String (UUID) | Ya | — | ID perusahaan |
| `name` | String | Ya | — | Nama customer |
| `email` | String | Tidak | — | Email customer |
| `phone` | String | Ya | — | Nomor telepon |
| `whatsapp_number` | String | Tidak | — | Nomor WhatsApp untuk follow up |
| `company` | String | Tidak | — | Nama perusahaan customer (B2B) |
| `address` | String | Tidak | — | Alamat |
| `customer_type` | Enum | Tidak | `individual` | Tipe customer |
| `membership_level_id` | String | Tidak | — | ID level membership |
| `membership_level_name` | String | Tidak | — | Nama level membership |
| `membership_since` | Date | Tidak | — | Sejak kapan menjadi member |
| `membership_points` | Number | Tidak | `0` | Total poin member saat ini |
| `lifetime_points` | Number | Tidak | `0` | Total poin sepanjang waktu |
| `stamps` | Number | Tidak | `0` | Total stamp yang dimiliki |
| `has_negative_points` | Boolean | Tidak | `false` | Flag saldo poin negatif |
| `loyalty_deficit_points` | Number | Tidak | `0` | Defisit poin loyalty |
| `status` | Enum | Tidak | `lead` | Status customer |
| `source` | String | Tidak | — | Sumber customer (ads, referral, walk-in) |
| `tags` | Array\[String] | Tidak | — | Tag/label customer |
| `lifetime_value` | Number | Tidak | `0` | Total nilai transaksi sepanjang waktu |
| `total_orders` | Number | Tidak | `0` | Jumlah total transaksi |
| `average_order_value` | Number | Tidak | `0` | Rata-rata nilai transaksi |
| `last_purchase_date` | Date | Tidak | — | Tanggal pembelian terakhir |
| `birthday` | Date | Tidak | — | Tanggal ulang tahun |
| `auth_user_id` | String | Tidak | — | ID autentikasi dari Auth provider |
| `addresses` | Array\[Object] | Tidak | — | Buku alamat tersimpan customer |
| `payment_terms` | Enum | Tidak | `net_30` | Ketentuan pembayaran B2B |
| `credit_limit` | Number | Tidak | `0` | Batas plafon riutang B2B |
| `is_active` | Boolean | Tidak | `true` | Status aktif customer |

#### CustomerMembership

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

#### WarehouseLocation

| Field | Tipe | Wajib | Default | Deskripsi |
| - | - | - | - | - |
| `company_id` | String (UUID) | Ya | — | ID perusahaan |
| `location_name` | String | Ya | — | Nama lokasi (Gudang Utama, Toko Cabang A, dll) |
| `location_code` | String | Ya | — | Kode unik lokasi |
| `location_type` | Enum | Tidak | `warehouse` | Tipe lokasi: `warehouse`, `store`, `transit`, `virtual` |
| `description` | String | Tidak | — | Penjelasan lokasi, kapasitas, dan jenis barang (maks 1000 karakter) |
| `address` | String | Tidak | — | Alamat lengkap |
| `city` | String | Tidak | — | Kota |
| `manager_name` | String | Tidak | — | Nama pengelola |
| `manager_contact` | String | Tidak | — | Kontak pengelola |
| `capacity` | Number | Tidak | — | Kapasitas maksimal (unit atau m2) |
| `current_utilization` | Number | Tidak | `0` | Utilisasi saat ini (%) |
| `is_active` | Boolean | Tidak | `true` | Status lokasi aktif |
| `coordinates` | Object | Tidak | — | Koordinat GPS (latitude, longitude) |

***

## Diagram Status (stateDiagram)

### Status MarketplaceOrder

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Order dibuat
    pending --> processing: Admin mulai memproses
    pending --> cancelled: Customer batal / timeout
    processing --> shipped: Barang dikirim
    processing --> cancelled: Dibatalkan saat proses
    shipped --> delivered: Diterima customer
    shipped --> cancelled: Retur / komplain
    delivered --> [*]: Selesai
    cancelled --> [*]: Akhir

    state pending {
        [*] --> Menunggu_Konfirmasi
        Menunggu_Konfirmasi --> Pembayaran_Verifikasi: Upload bukti bayar
        Pembayaran_Verifikasi --> [*]
    }

    state processing {
        [*] --> Disiapkan
        Disiapkan --> Pengemasan
        Pengemasan --> [*]
    }

    state shipped {
        [*] --> Dalam_Pengiriman
        Dalam_Pengiriman --> [*]
    }
```

### Status ProductOrder (Produk Digital)

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: Customer submit order
    pending --> processing: Admin mengambil order
    pending --> cancelled: Customer batal
    processing --> completed: Hasil dikirim ke customer
    processing --> cancelled: Admin batalkan
    completed --> [*]: Selesai
    cancelled --> [*]: Akhir

    state processing {
        [*] --> Dikerjakan
        Dikerjakan --> Review_Internal
        Review_Internal --> [*]
    }
```

***

## Diagram Sekuens (sequenceDiagram)

### Flow Checkout Marketplace (Produk Fisik)

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant MP as Marketplace.jsx
    participant CART as MarketplaceCart
    participant ORD as MarketplaceOrder
    participant INV as Inventory
    participant COMM as MarketplaceCommissionSettings

    C->>MP: Klik "Add to Cart"
    MP->>CART: Insert item (product_id, quantity)
    CART-->>MP: Cart updated

    C->>MP: Klik "Checkout"
    MP->>CART: Fetch all cart items
    CART-->>MP: List item di keranjang

    MP->>INV: aggregateAvailableStockByProduct()
    INV-->>MP: Stok tersedia per produk

    alt Stok mencukupi
        MP->>COMM: Get commission_rate
        COMM-->>MP: Rate komisi saat ini
        MP->>ORD: Create order (items, total, commission)
        ORD-->>MP: Order created
        MP->>CART: Clear cart items
        MP-->>C: Redirect ke halaman konfirmasi
    else Stok tidak mencukupi
        MP-->>C: Tampilkan pesan "Stok tidak cukup"
    end
```

### Flow Pemesanan Produk Digital

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant DP as DigitalProduct Detail
    participant FORM as Order Form
    participant PO as ProductOrder
    participant ADMIN as Admin Panel

    C->>DP: Pilih produk digital
    DP-->>C: Tampilkan detail + form fields

    C->>FORM: Isi form (order_form_fields)
    FORM->>FORM: Hitung final_price (diskon, voucher, tip)
    FORM->>PO: Submit order (product_id, order_data, final_price)
    PO-->>C: Konfirmasi order berhasil

    ADMIN->>PO: Fetch pending orders
    PO-->>ADMIN: List order pending
    ADMIN->>PO: Assign ke admin (assigned_to_admin)
    ADMIN->>PO: Upload result_files + update status
    PO-->>C: Notifikasi order selesai
```

### Flow Stock Aggregation dari Warehouse

```mermaid theme={null}
sequenceDiagram
    participant MP as Marketplace.jsx
    participant CP as CompanyPOSProduct
    participant WL as WarehouseLocation
    participant INV as CompanyPOSInventory
    participant AGG as Stock Aggregator

    MP->>CP: Fetch active products
    CP-->>MP: List produk aktif

    MP->>AGG: aggregateAvailableStockByProduct()

    loop Untuk setiap product_id
        AGG->>WL: Fetch warehouse locations (company_id)
        WL-->>AGG: List lokasi gudang aktif

        loop Untuk setiap warehouse
            AGG->>INV: Get latest stock (product_id, location)
            INV-->>AGG: stock_after terbaru
        end

        AGG->>AGG: Sum semua stock_after = total available
    end

    AGG-->>MP: Map { product_id: total_stock }
    MP->>MP: Merge stock ke product data
```

***

## Tabel Enum

### Enum `category` (DigitalProduct)

| Nilai | Deskripsi |
| - | - |
| `zoom` | Jasa Zoom/video conference |
| `design` | Jasa desain grafis (Canva, Photoshop, dll) |
| `video_editing` | Jasa editing video |
| `plagiarism_check` | Jasa cek plagiasme |
| `other` | Kategori lainnya |

### Enum `status` (MarketplaceOrder)

| Nilai | Deskripsi |
| - | - |
| `pending` | Order baru, menunggu konfirmasi pembayaran |
| `processing` | Order dikonfirmasi, sedang diproses/dikemas |
| `shipped` | Barang sudah dikirim ke kurir |
| `delivered` | Barang diterima oleh customer |
| `cancelled` | Order dibatalkan |

### Enum `payment_status` (MarketplaceOrder)

| Nilai | Deskripsi |
| - | - |
| `pending` | Pembayaran belum diterima |
| `paid` | Pembayaran sudah diterima |
| `refunded` | Pembayaran dikembalikan |

### Enum `status` (ProductOrder)

| Nilai | Deskripsi |
| - | - |
| `pending` | Order baru, menunggu admin mengambil |
| `processing` | Sedang dikerjakan oleh admin |
| `completed` | Hasil sudah dikirim ke customer |
| `cancelled` | Order dibatalkan |

### Enum `type` (CompanyPOSInventory)

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

### Enum `customer_type` (Customer)

| Nilai | Deskripsi |
| - | - |
| `individual` | Perorangan/konsumen akhir |
| `business` | Perusahaan/B2B |
| `retail` | Toko retail |
| `reseller` | Reseller/dropshipper |
| `distributor` | Distributor |
| `modern_market` | Pasar modern/minimarket |

### Enum `status` (Customer)

| Nilai | Deskripsi |
| - | - |
| `lead` | Calon customer, belum pernah transaksi |
| `prospect` | Menunjukkan minat, dalam pipeline |
| `customer` | Sudah pernah bertransaksi |
| `inactive` | Tidak aktif lagi |

### Enum `location_type` (WarehouseLocation)

| Nilai | Deskripsi |
| - | - |
| `warehouse` | Gudang utama |
| `store` | Toko/cabang retail |
| `transit` | Lokasi transit/perpindahan |
| `virtual` | Lokasi virtual (untuk tracking) |

### Enum `scheme_type` (CustomerMembership)

| Nilai | Deskripsi |
| - | - |
| `points` | Berdasarkan akumulasi poin belanja |
| `stamp` | Berdasarkan jumlah stamp per transaksi |
| `spending` | Berdasarkan total nominal belanja |
| `visits` | Berdasarkan frekuensi kunjungan |
| `hybrid` | Kombinasi beberapa skema |

### Enum `reward_type` (CustomerMembership)

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

### Enum `payment_terms` (Customer)

| Nilai | Deskripsi |
| - | - |
| `cash` | Bayar tunai |
| `net_7` | Jatuh tempo 7 hari |
| `net_14` | Jatuh tempo 14 hari |
| `net_30` | Jatuh tempo 30 hari |
| `net_60` | Jatuh tempo 60 hari |
| `custom` | Ketentuan khusus |

### Enum `field_type` (DigitalProduct.order\_form\_fields)

| Nilai | Deskripsi |
| - | - |
| `text` | Input teks biasa |
| `number` | Input angka |
| `email` | Input email |
| `file` | Upload file |
| `textarea` | Input teks panjang |
| `date` | Pilih tanggal |
| `time` | Pilih waktu |
| `select` | Dropdown pilihan |
| `radio` | Pilihan tunggal |
| `checkbox` | Pilihan ganda |

***

## Tabel RBAC (Row-Level Security)

Berikut adalah aturan akses per entity berdasarkan definisi RLS di base44:

| Entity | Create | Read | Update | Delete | Keterangan |
| - | - | - | - | - | - |
| `DigitalProduct` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `MarketplaceCart` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `MarketplaceCommissionSettings` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `MarketplaceOrder` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `ProductOrder` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `ShopSettings` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `CompanyPOSCategory` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `CompanyPOSInventory` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `CompanyPOSSettings` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `Customer` | Company-scoped / Creator / Admin | Company-scoped / Creator / Admin | Company-scoped / Creator / Admin | Company-scoped / Creator / Admin | Dibatasi per `company_id` aktif atau creator sendiri, admin full akses |
| `CustomerMembership` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |
| `WarehouseLocation` | Authenticated | Authenticated | Authenticated | Authenticated | Semua user terautentikasi bisa CRUD |

**Detail RLS Customer (Company-Scoped):**

Akses ke entity `Customer` dibatasi dengan kondisi OR berikut:

1. `data.company_id` = `user.data.active_company_id` DAN `company_id` tidak null/kosong
2. `created_by_id` = `user.id` (creator bisa akses data sendiri)
3. `user.role` = `admin` (admin memiliki akses penuh)

Aturan ini berlaku identik untuk operasi Create, Read, Update, dan Delete.


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