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

# Overview

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

***

title: "CRM & Pelanggan — Gambaran Umum"
description: "Arsitektur lengkap modul CRM SNISHOP ERP: 9 komponen (3.500+ baris), 7 entitas, 5 skema loyalty, 6-stage sales pipeline, WhatsApp gateway integration, AI copilot, dan import VCF/CSV/XLSX dengan fuzzy column matching."
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# CRM & Pelanggan — Gambaran Umum

<img src="https://mintlify.s3.us-west-1.amazonaws.com/quinnofspicy/docs/mintlify/screenshots/crm/overview.png" alt="CRM Overview" />

Modul CRM mengelola seluruh siklus hubungan pelanggan dari **lead acquisition** hingga **loyalty program**. Terintegrasi dengan WhatsApp gateway, AI auto-responder, dan sistem membership multi-level, modul ini terdiri dari **9 komponen React** (total 3.500+ baris), **7 entitas** dengan skema data yang kaya, dan integrasi lintas modul ke POS, Finance, Distribution, Invoice, Dashboard, B2B, dan AI Agent.

Sistem CRM SNISHOP ERP bukan sekadar database kontak — melainkan **platform customer intelligence** yang mampu melacak lifetime value, memprediksi churn, mengotomasi komunikasi via WhatsApp AI, dan mengelola pipeline penjualan dari prospect hingga deal closed.

## Arsitektur Modul

```mermaid theme={null}
graph TB
    subgraph TABS["4 Tab Utama — CRM.jsx (803 lines)"]
        CUST[Tab: Database<br/>CustomerAudienceList<br/>461 lines]
        PIPE[Tab: Pipeline<br/>SalesFunnelBoard<br/>715 lines]
        AI[Tab: Otomasi & AI<br/>AIChatBotSettings<br/>163 lines]
        LOY[Tab: Loyalty<br/>MembershipLevelManager<br/>821 lines]
    end

    subgraph STANDALONE["Halaman Terpisah"]
        SPM[Sales Pipeline<br/>Management<br/>963 lines]
    end

    subgraph FEATURES["Fitur CRM — 6 Komponen"]
        DET[Customer Detail<br/>Profile<br/>282 lines]
        WA[WhatsApp<br/>Quick Send<br/>185 lines]
        BLAST[Blast<br/>Message<br/>287 lines]
        IMP[Import<br/>VCF/CSV/XLSX<br/>655 lines]
        ADDR[Address<br/>Book<br/>211 lines]
        COPILOT[AI<br/>Copilot]
    end

    subgraph ENTITIES["7 Entitas CRM"]
        CU[Customer<br/>40+ fields]
        SP[SalesPipeline<br/>15+ fields]
        CM[CustomerMembership<br/>20+ fields]
        CA[CustomerAddress<br/>8+ fields]
        WS[WhatsAppSession<br/>6+ fields]
        CL[CustomerLoyaltyLedger<br/>7+ fields]
        CP[CustomerPO<br/>10+ fields]
    end

    CUST --> CU
    PIPE --> SP
    LOY --> CM
    SPM --> SP
    CUST --> DET
    CUST --> WA
    CUST --> BLAST
    CUST --> IMP
    DET --> ADDR
    DET --> COPILOT
    WA --> WS
    BLAST --> WS
    CU --> CL
    CU --> CA
    CU --> CP
```

## Halaman dalam Modul

| Halaman | Komponen | Lines | URL | Fungsi | Tab |
| - | - | - | - | - | - |
| **CRM** | `CRM.jsx` | 803 | `/crm` | Hub CRM utama | customers, sales\_funnel, ai\_settings, membership |
| **Sales Pipeline** | `SalesPipelineManagement.jsx` | 963 | `/sales-pipeline` | Pipeline management standalone | Kanban + List view |
| **Company Membership** | `MembershipLevelManager.jsx` | 821 | `/company-membership` | Konfigurasi membership perusahaan | — |
| **Customer Membership** | — | — | `/customer-membership` | Tampilan membership pelanggan | — |

## 7 Entitas CRM

| # | Entity | Fields | Deskripsi |
| - | - | - | - |
| 1 | **Customer** | 40+ | Database pelanggan dengan LTV, membership, contact tracking, B2B fields |
| 2 | **SalesPipeline** | 15+ | Lead tracking dengan 6 tahap pipeline + probability |
| 3 | **CustomerMembership** | 20+ | Tier membership definition dengan 5 skema loyalty |
| 4 | **CustomerAddress** | 8+ | Buku alamat multi-lokasi per pelanggan |
| 5 | **WhatsAppSession** | 6+ | Status koneksi WhatsApp gateway |
| 6 | **CustomerLoyaltyLedger** | 7+ | Ledger poin/stamp loyalty (earn/redeem/expire) |
| 7 | **CustomerPO** | 10+ | Purchase order pelanggan (B2B) |

### Customer Entity — 40+ Fields

```mermaid theme={null}
graph TB
    subgraph "Customer Entity — 365 lines schema"
        direction TB
        ID[Identity<br/>name, email, phone, photo_url]
        STATUS[Status<br/>customer / prospect / lead / inactive]
        FIN[Financial<br/>lifetime_value, total_orders, avg_order_value]
        MEM[Membership<br/>membership_level_id, membership_points, membership_since]
        CONTACT[Contact Tracking<br/>last_contact_date, last_contact_method, last_contact_notes]
        ADDR[Address<br/>default_address_id → CustomerAddress]
        B2B[B2B Fields<br/>company_name, npwp, payment_terms, credit_limit]
        META[Metadata<br/>source, medium, campaign, company_id]
    end
```

| Group | Fields | Deskripsi |
| - | - | - |
| **Identity** | `name`, `email`, `phone`, `photo_url` | Data kontak dasar |
| **Status** | `status` | Enum: customer / prospect / lead / inactive |
| **Financial** | `lifetime_value`, `total_orders`, `avg_order_value` | Metrik keuangan akumulatif |
| **Membership** | `membership_level_id`, `membership_points`, `lifetime_points`, `membership_since` | Data loyalty |
| **Contact Tracking** | `last_contact_date`, `last_contact_method`, `last_contact_notes` | Kapan & bagaimana terakhir dihubungi |
| **Address** | `default_address_id` | Link ke CustomerAddress |
| **B2B** | `company_name`, `npwp`, `payment_terms`, `credit_limit` | Data bisnis untuk B2B customers |
| **Metadata** | `source`, `medium`, `campaign`, `company_id` | Acquisition tracking + multi-tenant |

## Dashboard Stats

| Metrik | Sumber | Keterangan |
| - | - | - |
| **Total Pelanggan** | `COUNT(Customer)` | Seluruh pelanggan terdaftar |
| **Pelanggan Aktif** | `WHERE status = 'customer'` | Status aktif |
| **Total Pendapatan** | `SUM(lifetime_value)` | Akumulasi dari semua pelanggan |
| **Rata-rata LTV** | `total_revenue / total_customers` | Rata-rata nilai per pelanggan |
| **VIP Count** | `WHERE lifetime_value > 1.000.000` | Pelanggan bernilai tinggi |
| **Prospek** | `WHERE status = 'prospect'` | Calon pelanggan |
| **Lead** | `WHERE status = 'lead'` | Lead baru |

## Status Filter

| Status | Warna | Badge | Deskripsi |
| - | - | - | - |
| `customer` | Emerald | 🟢 | Pelanggan aktif, sudah pernah transaksi |
| `prospect` | Blue | 🔵 | Calon pelanggan, belum transaksi |
| `lead` | Amber | 🟡 | Lead baru masuk dari channel marketing |
| `inactive` | Gray | ⚫ | Tidak aktif > 90 hari |

## 9 Komponen CRM

| # | Komponen | Lines | Fungsi |
| - | - | - | - |
| 1 | `CRM.jsx` | 803 | Hub utama — 4 tab + dashboard stats |
| 2 | `SalesPipelineManagement.jsx` | 963 | Standalone pipeline (Kanban + List) |
| 3 | `MembershipLevelManager.jsx` | 821 | CRUD tier membership (5 skema) |
| 4 | `SalesFunnelBoard.jsx` | 715 | Embedded Kanban pipeline |
| 5 | `CustomerImportModal.jsx` | 655 | Import VCF/CSV/XLSX + fuzzy matching |
| 6 | `CustomerAudienceList.jsx` | 461 | Tabel pelanggan paginasi (25/page) |
| 7 | `CustomerBlastMessage.jsx` | 287 | Broadcast WhatsApp + AI generation |
| 8 | `CustomerDetailProfile.jsx` | 282 | Dialog detail — 3 tabs (profile, transactions, notes) |
| 9 | `CustomerAddressBook.jsx` | 211 | Buku alamat multi-lokasi |
| 10 | `WhatsAppQuickSend.jsx` | 185 | Kirim WhatsApp ke 1 pelanggan |
| 11 | `AIChatBotSettings.jsx` | 163 | Konfigurasi WhatsApp gateway + AI |

## Sales Pipeline — 6 Stages

```mermaid theme={null}
stateDiagram-v2
    [*] --> prospect: Lead masuk
    prospect --> qualified: Verifikasi
    qualified --> proposal: Kirim penawaran
    proposal --> negotiation: Negosiasi
    negotiation --> won: Deal closed ✅
    negotiation --> lost: Deal gagal ❌

    won --> [*]
    lost --> [*]
```

| Stage | Probability | Deskripsi |
| - | - | - |
| **prospect** | 10% | Lead baru, belum diverifikasi |
| **qualified** | 25% | Budget & kebutuhan terkonfirmasi |
| **proposal** | 50% | Proposal sudah dikirim |
| **negotiation** | 75% | Sedang negosiasi |
| **won** | 100% | Deal closed |
| **lost** | 0% | Deal gagal |

## Loyalty — 5 Skema

| Skema | Kriteria | Contoh |
| - | - | - |
| **spending** | Total belanja ≥ threshold | Silver: 1jt, Gold: 5jt |
| **stamp** | Stamp ≥ threshold | 10 stamp = free item |
| **points** | Poin ≥ threshold | 1000 poin = Gold |
| **visits** | Kunjungan ≥ threshold | 20 visits = VIP |
| **hybrid** | Any criteria met | Flexible combination |

## Customer Import — Fuzzy Column Matching

```mermaid theme={null}
flowchart TD
    A[Upload File<br/>VCF / CSV / XLSX] --> B[Parse File]
    B --> C{File Type?}
    C -->|VCF| D[Parse vCard format<br/>Extract FN, TEL, EMAIL]
    C -->|CSV| E[Parse CSV headers]
    C -->|XLSX| F[Parse Excel headers]
    E --> G[Fuzzy Column Matching<br/>Indonesian + English]
    F --> G
    D --> H[Preview Import]
    G --> H
    H --> I{Confirm?}
    I -->|Ya| J[Batch Create Customers]
    I -->|Tidak| K[Cancel]
```

| Kolom Target | Fuzzy Matches (ID + EN) |
| - | - |
| `name` | "Nama", "Name", "Nama Lengkap", "Full Name" |
| `email` | "Email", "E-mail", "Surel" |
| `phone` | "Telepon", "Phone", "No HP", "Mobile", "WhatsApp" |
| `address` | "Alamat", "Address", "Alamat Lengkap" |
| `company` | "Perusahaan", "Company", "Nama Perusahaan" |

## WhatsApp Integration

| Fitur | Komponen | Fungsi |
| - | - | - |
| **Quick Send** | `WhatsAppQuickSend` (185 lines) | Kirim pesan ke 1 pelanggan |
| **Blast Message** | `CustomerBlastMessage` (287 lines) | Broadcast ke banyak pelanggan + AI generate |
| **AI Copilot** | `CustomerDetailProfile` | Generate pesan personal via LLM |
| **Gateway Config** | `AIChatBotSettings` (163 lines) | Konfigurasi Wablas/Fonnte |
| **Auto-Responder** | `AIChatBotSettings` | AI balas pesan otomatis |

Setelah mengirim WhatsApp, sistem otomatis update `Customer`:

* `last_contact_date` → waktu kirim
* `last_contact_method` → "whatsapp"
* `last_contact_notes` → isi pesan

## AI Integration

| Lokasi | Fungsi AI | Implementasi |
| - | - | - |
| **AI Chatbot Settings** | Konfigurasi auto-responder | System prompt + LLM |
| **AI Copilot** | Generate pesan WhatsApp personal | Context-aware LLM call |
| **Blast Message** | Generate pesan broadcast | Audience-context LLM |
| **AI Agent** | Autonomous customer creation | Tool: `create_customer` |

## Integrasi Lintas Modul

```mermaid theme={null}
flowchart LR
    POS[POS<br/>Transaction] -->|Membership discount<br/>Loyalty stamps| CRM
    FIN[Finance<br/>AR Aging] -->|Receivables by customer| CRM
    DIST[Distribution] -->|Retailer tracking| CRM
    INV[Invoice] -->|Customer name/email matching| CRM
    DASH[Dashboard] -->|Sales & marketing metrics| CRM
    B2B[B2B Portal] -->|Partner data| CRM
    AI[AI Agent] -->|Autonomous create/filter| CRM
```

| Dari | Ke | Data | Trigger |
| - | - | - | - |
| POS | CRM | Membership discount, loyalty stamps | Setiap transaksi dengan member |
| Finance | CRM | Receivables aging by customer | AR report generation |
| Distribution | CRM | Retailer monitoring | Retailer sync |
| Invoice | CRM | Customer name/email matching | Invoice creation |
| Dashboard | CRM | Sales & marketing metrics | Dashboard load |
| AI Agent | CRM | Autonomous customer creation | AI tool invocation |

## Best Practices

1. **Import data pelanggan secara berkala** dari sumber eksternal (Google Contacts, Excel) menggunakan fitur import VCF/CSV/XLSX
2. **Gunakan pipeline Kanban** untuk tracking deal harian — drag-and-drop untuk advance stage
3. **Monitor Weighted Forecast** untuk prediksi revenue yang lebih akurat
4. **Set up WhatsApp auto-responder** untuk menangani pertanyaan dasar di luar jam kerja
5. **Review overdue leads** setiap pagi untuk memastikan tidak ada deal yang terlewat
6. **Gunakan AI Copilot** untuk generate pesan WhatsApp yang personal dan kontekstual
7. **Export data pelanggan** secara berkala sebagai backup
8. **Link POS membership** ke CRM untuk memastikan poin dan tier ter-sync real-time

***

## Entity Relationship Diagram

```mermaid theme={null}
erDiagram
    Customer ||--o{ CustomerAddress : "memiliki (addresses[])"
    Customer ||--o| CustomerMembership : "memiliki tier"
    Customer ||--o{ CustomerLoyaltyLedger : "memiliki mutasi"
    Customer ||--o{ SalesPipeline : "sebagai lead"
    Customer ||--o{ CustomerPO : "membuat PO"
    Customer }o--o| WhatsAppSession : "terhubung via"
    CustomerMembership ||--o{ Customer : "diterapkan ke"
    CustomerMembership ||--o{ CustomerLoyaltyLedger : "menentukan skema"
    CustomerPO }o--o| Invoice : "di-generate ke"
    SalesPipeline }o--o| Customer : "assigned_to sales"

    Customer {
        string company_id PK
        string name "required"
        string phone "required"
        string email
        string whatsapp_number
        string customer_type "enum"
        string status "enum"
        number lifetime_value
        number total_orders
        string membership_level_id FK
        number membership_points
    }

    CustomerMembership {
        string company_id PK
        string level_name "required"
        string level_key "required"
        string scheme_type "enum"
        number discount_percentage
        number points_multiplier
        string reward_type "enum"
    }

    CustomerAddress {
        string id PK
        string label
        string recipient_name
        string full_address
        string city_district
        string postal_code
        boolean is_default
    }

    CustomerLoyaltyLedger {
        string company_id PK
        string customer_id FK "required"
        string idempotency_key "required"
        string event_type "enum"
        string scheme_type "enum"
        number points_delta
        number stamps_delta
    }

    SalesPipeline {
        string company_id PK
        string lead_name "required"
        string assigned_to "required"
        string pipeline_stage "enum"
        number estimated_value
        number probability
        string source_lead "enum"
    }

    WhatsAppSession {
        string user_id "required"
        string user_email "required"
        string company_id
        string status "enum"
        string phone_number
        number total_messages_sent
    }

    CustomerPO {
        string company_id PK
        string customer_id FK "required"
        string po_number "required"
        date po_date "required"
        string status "enum"
        number total_amount
        string invoice_id FK
    }
```

***

## Entity Schema Tables

### 1. Customer — 44 Fields

Entitas utama yang menyimpan seluruh data pelanggan, termasuk informasi kontak, membership, loyalty, financial metrics, B2B fields, dan alamat tersimpan.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan (multi-tenant) |
| `name` | string | Ya | Nama lengkap pelanggan |
| `phone` | string | Ya | Nomor telepon / kontak utama |
| `email` | string | Tidak | Alamat email pelanggan |
| `whatsapp_number` | string | Tidak | Nomor WhatsApp untuk follow up |
| `company` | string | Tidak | Nama perusahaan / tempat kerja pelanggan |
| `address` | string | Tidak | Alamat utama (teks bebas) |
| `customer_type` | enum | Tidak | Tipe pelanggan: `individual`, `business`, `retail`, `reseller`, `distributor`, `modern_market`. Default: `individual` |
| `membership_level_id` | string | Tidak | ID level membership customer (FK ke CustomerMembership) |
| `membership_level_name` | string | Tidak | Nama level membership (cache denormalized) |
| `membership_since` | date | Tidak | Sejak kapan jadi member |
| `membership_points` | number | Tidak | Total poin member saat ini. Default: `0` |
| `lifetime_points` | number | Tidak | Total poin sepanjang waktu (tidak berkurang saat redeem). Default: `0` |
| `stamps` | number | Tidak | Total stamp yang dimiliki saat ini. Default: `0` |
| `has_negative_points` | boolean | Tidak | Flag saldo negatif akibat pembatalan/void transaksi setelah poin terpakai. Default: `false` |
| `loyalty_deficit_points` | number | Tidak | Defisit poin loyalty yang membutuhkan rekonsiliasi manual. Default: `0` |
| `loyalty_review_notes` | string | Tidak | Catatan review penanganan loyalty deficit |
| `last_tier_upgrade_date` | date-time | Tidak | Waktu perubahan level membership terakhir |
| `last_tier_upgrade_reason` | string | Tidak | Alasan perubahan level membership |
| `last_tier_upgrade_by` | string | Tidak | Email operator / admin yang melakukan perubahan tier |
| `status` | enum | Tidak | Status pelanggan: `lead`, `prospect`, `customer`, `inactive`. Default: `lead` |
| `source` | string | Tidak | Sumber customer (ads, referral, walk-in, dll) |
| `tags` | string\[] | Tidak | Tag / label kategorisasi pelanggan |
| `lifetime_value` | number | Tidak | Total nilai transaksi sepanjang waktu (LTV). Default: `0` |
| `total_orders` | number | Tidak | Jumlah total transaksi. Default: `0` |
| `average_order_value` | number | Tidak | Rata-rata nilai transaksi. Default: `0` |
| `last_purchase_date` | date | Tidak | Tanggal pembelian terakhir |
| `last_contact_date` | date | Tidak | Terakhir kali di-follow up |
| `last_contact_method` | enum | Tidak | Metode terakhir follow up: `phone`, `whatsapp`, `email`, `meeting` |
| `last_contact_notes` | string | Tidak | Catatan follow up terakhir |
| `birthday` | date | Tidak | Tanggal ulang tahun pelanggan |
| `preferences` | object | Tidak | Preferensi pelanggan (sub-object) |
| `preferences.preferred_contact` | enum | Tidak | Kontak pilihan: `phone`, `whatsapp`, `email` |
| `preferences.preferred_payment` | string | Tidak | Metode pembayaran pilihan |
| `preferences.language` | string | Tidak | Bahasa pilihan. Default: `id` |
| `notes` | string | Tidak | Catatan umum mengenai pelanggan |
| `auth_user_id` | string | Tidak | ID autentikasi dari Auth provider |
| `default_address_id` | string | Tidak | ID alamat default di buku alamat |
| `addresses` | object\[] | Tidak | Buku alamat tersimpan customer (embedded array, lihat CustomerAddress) |
| `billing_address` | string | Tidak | Alamat penagihan resmi perusahaan / customer B2B |
| `shipping_address` | string | Tidak | Alamat pengiriman default B2B |
| `parent_customer_id` | string | Tidak | ID perusahaan induk untuk outlet/cabang retailer |
| `outlet_name` | string | Tidak | Nama cabang / outlet |
| `tax_id` | string | Tidak | NPWP / Identitas administrasi pajak |
| `payment_terms` | enum | Tidak | Ketentuan pembayaran B2B: `cash`, `net_7`, `net_14`, `net_30`, `net_60`, `custom`. Default: `net_30` |
| `payment_terms_days` | number | Tidak | Jumlah hari jatuh tempo pembayaran. Default: `30` |
| `credit_limit` | number | Tidak | Batas plafon riutang kredit B2B. Default: `0` |
| `is_active` | boolean | Tidak | Status aktif customer B2B. Default: `true` |

### 2. SalesPipeline — 19 Fields

Entitas untuk melacak lead dan deal melalui 6 tahap pipeline penjualan, dari prospek hingga won/lost.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan (multi-tenant) |
| `lead_id` | string | Tidak | ID prospek/lead (FK ke Customer jika sudah dikonversi) |
| `lead_name` | string | Ya | Nama prospek / kontak utama |
| `lead_email` | string | Tidak | Email prospek |
| `lead_phone` | string | Tidak | Nomor telepon prospek |
| `company_name` | string | Tidak | Nama perusahaan prospek |
| `assigned_to` | string | Ya | ID sales person yang menangani |
| `pipeline_stage` | enum | Tidak | Stage dalam sales pipeline: `prospect`, `qualified`, `proposal`, `negotiation`, `won`, `lost`. Default: `prospect` |
| `estimated_value` | number | Tidak | Perkiraan nilai penjualan (potensi revenue) |
| `currency` | string | Tidak | Mata uang estimasi. Default: `IDR` |
| `probability` | number | Tidak | Probabilitas konversi (%). Default: `0` |
| `expected_close_date` | date | Tidak | Tanggal penutupan yang diharapkan |
| `actual_close_date` | date | Tidak | Tanggal aktual deal ditutup |
| `source_lead` | enum | Tidak | Sumber prospek: `website`, `referral`, `advertisement`, `cold_call`, `social_media`, `event`, `other` |
| `product_interest` | string\[] | Tidak | Daftar produk yang diminati prospek |
| `interaction_history` | object\[] | Tidak | Riwayat interaksi dengan prospek |
| `interaction_history[].interaction_date` | date | Tidak | Tanggal interaksi |
| `interaction_history[].interaction_type` | enum | Tidak | Tipe interaksi: `call`, `email`, `meeting`, `message` |
| `interaction_history[].notes` | string | Tidak | Catatan hasil interaksi |
| `interaction_history[].done_by` | string | Tidak | Siapa yang melakukan interaksi |
| `next_action` | string | Tidak | Aksi selanjutnya yang harus dilakukan |
| `next_action_date` | date | Tidak | Tanggal aksi selanjutnya harus dilakukan |
| `notes` | string | Tidak | Catatan umum mengenai deal ini |

### 3. CustomerMembership — 26 Fields

Definisi tier membership dengan 5 skema loyalty, benefit, diskon, dan aturan reward.

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

### 4. CustomerAddress — 10 Fields (Embedded)

Buku alamat multi-lokasi per pelanggan. Disimpan sebagai array embedded di dalam entitas Customer pada field `addresses`.

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | string | Ya | ID unik alamat |
| `label` | string | Tidak | Label alamat (Rumah, Kantor, Gudang, dll) |
| `recipient_name` | string | Tidak | Nama penerima di alamat ini |
| `recipient_phone` | string | Tidak | Nomor telepon penerima |
| `full_address` | string | Tidak | Alamat lengkap |
| `city_district` | string | Tidak | Kota / kecamatan |
| `postal_code` | string | Tidak | Kode pos |
| `notes` | string | Tidak | Catatan tambahan (patokan, dll) |
| `is_default` | boolean | Tidak | Flag alamat default. Default: `false` |
| `created_at` | string | Tidak | Waktu alamat dibuat |
| `updated_at` | string | Tidak | Waktu alamat terakhir diperbarui |

### 5. WhatsAppSession — 12 Fields

Status koneksi WhatsApp gateway per user, termasuk QR code, statistik pesan, dan data session terenkripsi.

| Field | Type | Required | Description |
| - | - | - | - |
| `user_id` | string | Ya | ID user pemilik session |
| `user_email` | string | Ya | Email user |
| `company_id` | string | Tidak | ID company (null untuk personal) |
| `session_id` | string | Tidak | Unique session ID |
| `phone_number` | string | Tidak | Nomor WhatsApp yang terkoneksi |
| `description` | string | Tidak | Catatan mengenai sesi WhatsApp ini dan tujuan penggunaannya (max 1000 karakter) |
| `status` | enum | Tidak | Status koneksi: `disconnected`, `connecting`, `connected`, `error`. Default: `disconnected` |
| `qr_code` | string | Tidak | QR Code untuk scan (base64 atau URL) |
| `last_connected` | date-time | Tidak | Terakhir kali connect |
| `total_contacts` | number | Tidak | Jumlah kontak tersinkronisasi. Default: `0` |
| `total_messages_sent` | number | Tidak | Total pesan terkirim. Default: `0` |
| `session_data` | object | Tidak | Data session WhatsApp (encrypted) |

### 6. CustomerLoyaltyLedger — 23 Fields

Ledger mutasi poin/stamp loyalty per customer — mencatat setiap earn, redeem, reversal, dan adjustment beserta snapshot saldo sebelum dan sesudah.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan (multi-tenant) |
| `customer_id` | string | Ya | ID customer pemilik transaksi loyalty |
| `customer_name` | string | Tidak | Nama customer saat transaksi (snapshot) |
| `customer_phone` | string | Tidak | Nomor telepon / kontak customer |
| `transaction_id` | string | Tidak | ID transaksi POS atau referensi sale asal |
| `transaction_number` | string | Tidak | Nomor struk / receipt number transaksi |
| `idempotency_key` | string | Ya | Idempotency key untuk mencegah double crediting / posting |
| `event_type` | enum | Ya | Tipe event mutasi loyalty: `earn`, `redeem`, `return_reversal`, `tier_upgrade`, `manual_adjustment` |
| `scheme_type` | enum | Tidak | Skema loyalty yang berlaku: `points`, `stamp`, `spending`, `visits`, `hybrid`. Default: `points` |
| `points_delta` | number | Tidak | Perubahan poin (+ bertambah, - berkurang). Default: `0` |
| `points_before` | number | Tidak | Saldo poin sebelum event. Default: `0` |
| `points_after` | number | Tidak | Saldo poin setelah event. Default: `0` |
| `stamps_delta` | number | Tidak | Perubahan stamp (+ bertambah, - berkurang). Default: `0` |
| `stamps_before` | number | Tidak | Saldo stamp sebelum event. Default: `0` |
| `stamps_after` | number | Tidak | Saldo stamp setelah event. Default: `0` |
| `eligible_amount` | number | Tidak | Nilai transaksi yang sah / eligible untuk perolehan poin. Default: `0` |
| `reason` | string | Tidak | Alasan atau keterangan mutasi loyalty |
| `deficit_points` | number | Tidak | Defisit poin jika poin telah terpakai sebelum void/return. Default: `0` |
| `requires_manual_review` | boolean | Tidak | Flag jika transaksi reversal menghasilkan saldo negatif dan butuh review manual. Default: `false` |
| `reward_details` | object | Tidak | Detail reward jika event adalah redeem |
| `reward_details.reward_type` | string | Tidak | Jenis reward yang ditukarkan |
| `reward_details.discount_amount` | number | Tidak | Nominal diskon dari reward |
| `reward_details.discount_percentage` | number | Tidak | Persentase diskon dari reward |
| `reward_details.product_id` | string | Tidak | ID produk reward (jika free\_product) |
| `reward_details.product_name` | string | Tidak | Nama produk reward |
| `reward_details.cost_points` | number | Tidak | Biaya poin untuk reward ini |
| `reward_details.cost_stamps` | number | Tidak | Biaya stamp untuk reward ini |
| `performed_by` | string | Tidak | Email atau ID operator yang memproses |
| `actor_role` | string | Tidak | Role aktor (owner, admin, cashier) |
| `created_at` | date-time | Tidak | Waktu event tercatat di server |
| `finance_record_id` | string | Tidak | ID FinancialRecord (biaya COGS reward) yang terhubung, diisi saat event redeem |

### 7. CustomerPO — 22 Fields

Purchase order dari customer B2B — melacak item yang dipesan, status pengiriman, dan integrasi ke Invoice.

| Field | Type | Required | Description |
| - | - | - | - |
| `company_id` | string | Ya | ID perusahaan (multi-tenant) |
| `customer_id` | string | Ya | ID customer B2B terkait |
| `customer_name` | string | Tidak | Nama customer / kontak pemesan |
| `customer_company` | string | Tidak | Nama badan usaha / toko customer B2B |
| `customer_email` | string | Tidak | Email customer |
| `customer_phone` | string | Tidak | Nomor telepon customer |
| `customer_address` | string | Tidak | Alamat customer |
| `po_number` | string | Ya | Nomor Purchase Order dari customer (unik per company & customer) |
| `po_date` | date | Ya | Tanggal PO diterbitkan oleh customer |
| `expected_delivery_date` | date | Tidak | Target tanggal pengiriman pesanan |
| `attachment_url` | string | Tidak | URL dokumen fisik / PDF bukti Purchase Order dari customer |
| `items` | object\[] | Ya | Daftar produk yang dipesan oleh customer |
| `items[].product_id` | string | Tidak | ID produk |
| `items[].sku` | string | Tidak | SKU produk |
| `items[].product_name` | string | Ya | Nama produk |
| `items[].variant` | string | Tidak | Varian produk |
| `items[].unit` | string | Tidak | Satuan unit. Default: `botol` |
| `items[].quantity` | number | Ya | Jumlah yang dipesan |
| `items[].unit_price` | number | Ya | Harga per unit |
| `items[].subtotal` | number | Tidak | Subtotal baris (quantity x unit\_price) |
| `items[].delivered_quantity` | number | Tidak | Jumlah yang sudah dikirim. Default: `0` |
| `subtotal` | number | Tidak | Subtotal keseluruhan PO. Default: `0` |
| `tax_percentage` | number | Tidak | Persentase pajak. Default: `0` |
| `tax_amount` | number | Tidak | Nominal pajak. Default: `0` |
| `discount_amount` | number | Tidak | Nominal diskon. Default: `0` |
| `total_amount` | number | Tidak | Total amount setelah pajak dan diskon. Default: `0` |
| `notes` | string | Tidak | Catatan atau instruksi khusus pesanan |
| `status` | enum | Tidak | Status siklus hidup PO: `draft`, `confirmed`, `invoiced`, `partially_delivered`, `delivered`, `cancelled`. Default: `draft` |
| `invoice_id` | string | Tidak | ID Invoice terkait yang di-generate dari Customer PO ini |
| `idempotency_key` | string | Tidak | Idempotency key saat membuat atau mengimpor PO |

***

## State Machines

### SalesPipeline — Siklus Hidup Deal

```mermaid theme={null}
stateDiagram-v2
    [*] --> prospect: Lead baru masuk

    prospect --> qualified: Verifikasi budget & kebutuhan
    prospect --> lost: Tidak qualified

    qualified --> proposal: Kirim penawaran formal
    qualified --> lost: Budget tidak sesuai

    proposal --> negotiation: Ada feedback / counter
    proposal --> lost: Proposal ditolak

    negotiation --> won: Deal closed - PO diterima
    negotiation --> lost: Negosiasi gagal

    won --> [*]: Konversi ke Invoice / CustomerPO
    lost --> [*]: Arsipkan sebagai deal gagal
```

| Transisi | Probability Change | Trigger |
| - | - | - |
| `prospect` → `qualified` | 10% → 25% | Verifikasi budget, kebutuhan, dan timeline |
| `qualified` → `proposal` | 25% → 50% | Proposal / quotation dikirim ke prospek |
| `proposal` → `negotiation` | 50% → 75% | Prospek memberikan feedback atau counter-offer |
| `negotiation` → `won` | 75% → 100% | Deal closed, PO diterima |
| `negotiation` → `lost` | 75% → 0% | Negosiasi gagal, prospek memilih kompetitor |
| `*` → `lost` | → 0% | Deal gagal di tahap manapun |

### CustomerPO — Siklus Hidup Purchase Order

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft: PO dibuat / diimpor

    draft --> confirmed: PO dikonfirmasi oleh admin
    draft --> cancelled: PO dibatalkan

    confirmed --> invoiced: Invoice di-generate dari PO
    confirmed --> cancelled: PO dibatalkan

    invoiced --> partially_delivered: Sebagian barang dikirim
    invoiced --> delivered: Semua barang dikirim

    partially_delivered --> delivered: Sisa barang dikirim

    delivered --> [*]: PO selesai
    cancelled --> [*]: PO dibatalkan
```

| Status | Deskripsi | Aksi Selanjutnya |
| - | - | - |
| `draft` | PO baru dibuat, belum dikonfirmasi | Confirm atau Cancel |
| `confirmed` | PO sudah dikonfirmasi, siap diproses | Generate Invoice atau Cancel |
| `invoiced` | Invoice sudah di-generate dari PO | Tandai pengiriman (partial/full) |
| `partially_delivered` | Sebagian barang sudah dikirim | Kirim sisa barang |
| `delivered` | Semua barang sudah dikirim | Selesai |
| `cancelled` | PO dibatalkan | Tidak ada (terminal state) |

### WhatsAppSession — Siklus Hidup Koneksi

```mermaid theme={null}
stateDiagram-v2
    [*] --> disconnected: Session dibuat

    disconnected --> connecting: Initiate pairing / scan QR
    connecting --> connected: QR di-scan, auth berhasil
    connecting --> error: Koneksi gagal / timeout

    connected --> disconnected: Logout / session expired
    connected --> error: Koneksi terputus

    error --> connecting: Retry koneksi
    error --> disconnected: Reset session
```

***

## Enum Reference Tables

### customer.status — Status Pelanggan

| Nilai | Deskripsi | Badge |
| - | - | - |
| `lead` | Lead baru masuk dari channel marketing, belum di-follow up | Amber |
| `prospect` | Calon pelanggan yang sedang di-follow up, belum transaksi | Blue |
| `customer` | Pelanggan aktif, sudah pernah melakukan transaksi | Emerald |
| `inactive` | Tidak aktif > 90 hari, tidak ada interaksi terbaru | Gray |

### customer.customer\_type — Tipe Pelanggan

| Nilai | Deskripsi |
| - | - |
| `individual` | Pelanggan perorangan / konsumen akhir |
| `business` | Pelanggan perusahaan / korporat |
| `retail` | Toko retail / warung |
| `reseller` | Reseller yang menjual kembali produk |
| `distributor` | Distributor / agen wilayah |
| `modern_market` | Minimarket / supermarket / pasar modern |

### customer.last\_contact\_method — Metode Kontak Terakhir

| Nilai | Deskripsi |
| - | - |
| `phone` | Terakhir dihubungi via telepon |
| `whatsapp` | Terakhir dihubungi via WhatsApp |
| `email` | Terakhir dihubungi via email |
| `meeting` | Terakhir dihubungi via pertemuan langsung |

### customer.payment\_terms — Ketentuan Pembayaran B2B

| Nilai | Deskripsi | Jatuh Tempo |
| - | - | - |
| `cash` | Pembayaran tunai / di tempat | 0 hari |
| `net_7` | Tempo 7 hari | 7 hari |
| `net_14` | Tempo 14 hari | 14 hari |
| `net_30` | Tempo 30 hari (default) | 30 hari |
| `net_60` | Tempo 60 hari | 60 hari |
| `custom` | Tempo kustom (lihat `payment_terms_days`) | Sesuai `payment_terms_days` |

### salespipeline.pipeline\_stage — Tahap Pipeline Penjualan

| Nilai | Probability | Deskripsi |
| - | - | - |
| `prospect` | 10% | Lead baru, belum diverifikasi kelayakannya |
| `qualified` | 25% | Budget & kebutuhan terkonfirmasi, prospek layak |
| `proposal` | 50% | Proposal / quotation sudah dikirim ke prospek |
| `negotiation` | 75% | Sedang negosiasi harga, termin, atau kontrak |
| `won` | 100% | Deal closed berhasil, PO diterima |
| `lost` | 0% | Deal gagal — prospek batal atau memilih kompetitor |

### salespipeline.source\_lead — Sumber Prospek

| Nilai | Deskripsi |
| - | - |
| `website` | Prospek datang dari website / landing page |
| `referral` | Referensi dari pelanggan atau mitra existing |
| `advertisement` | Dari iklan (Google Ads, Meta Ads, dll) |
| `cold_call` | Hasil dari outbound cold calling |
| `social_media` | Dari media sosial (Instagram, TikTok, LinkedIn) |
| `event` | Dari event / pameran / webinar |
| `other` | Sumber lainnya |

### salespipeline.interaction\_type — Tipe Interaksi

| Nilai | Deskripsi |
| - | - |
| `call` | Interaksi via telepon |
| `email` | Interaksi via email |
| `meeting` | Pertemuan tatap muka |
| `message` | Pesan singkat (WhatsApp, chat) |

### customermembership.scheme\_type — Skema Loyalty

| Nilai | Deskripsi | Kriteria Kenaikan Tier |
| - | - | - |
| `points` | Akumulasi poin dari setiap transaksi | Poin ≥ threshold |
| `stamp` | Akumulasi stamp per transaksi | Stamp ≥ `stamps_required` |
| `spending` | Total nominal belanja | Spending ≥ `min_purchase` |
| `visits` | Jumlah kunjungan / frekuensi transaksi | Visits ≥ threshold |
| `hybrid` | Kombinasi dari beberapa skema | Any criteria met |

### customermembership.reward\_type — Jenis Reward

| Nilai | Deskripsi |
| - | - |
| `none` | Tidak ada reward yang dapat ditukarkan |
| `discount_percentage` | Reward berupa diskon persentase |
| `discount_amount` | Reward berupa diskon nominal tetap |
| `free_product` | Reward berupa produk gratis (lihat `reward_product_id`) |

### customerloyaltyledger.event\_type — Tipe Event Mutasi Loyalty

| Nilai | Deskripsi | Arah Mutasi |
| - | - | - |
| `earn` | Perolehan poin/stamp dari transaksi | + (tambah) |
| `redeem` | Penukaran poin/stamp untuk reward | - (kurang) |
| `return_reversal` | Pembatalan/return transaksi yang membalik poin | - (reversal) |
| `tier_upgrade` | Bonus poin akibat naik tier | + (bonus) |
| `manual_adjustment` | Penyesuaian manual oleh admin/operator | +/- (adjustment) |

### customerpo.status — Status Purchase Order

| Nilai | Deskripsi | Terminal? |
| - | - | - |
| `draft` | PO baru dibuat, belum dikonfirmasi | Tidak |
| `confirmed` | PO sudah dikonfirmasi oleh admin | Tidak |
| `invoiced` | Invoice sudah di-generate dari PO ini | Tidak |
| `partially_delivered` | Sebagian barang sudah dikirim | Tidak |
| `delivered` | Semua barang sudah dikirim | Ya |
| `cancelled` | PO dibatalkan | Ya |

### whatsappsession.status — Status Koneksi WhatsApp

| Nilai | Deskripsi |
| - | - |
| `disconnected` | Session belum terhubung atau sudah logout |
| `connecting` | Sedang proses pairing / menunggu scan QR |
| `connected` | WhatsApp terhubung dan aktif |
| `error` | Koneksi error / terputus tidak normal |


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