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

# Appointments

<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: "Appointments"
description: "Sistem appointment dengan calendar view, 6 status, payment tracking, service management, dan ERP access control di SNISHOP ERP."
----------------------------------------------------------------------------------------------------------------------------------------------

# Appointments

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/productivity/appointments.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=f16ecfd331a365a177dfc29cfe10a497" alt="Appointments" width="1920" height="1080" data-path="docs/mintlify/screenshots/productivity/appointments.png" />

Halaman Appointments mengelola seluruh jadwal appointment bisnis kamu — dari booking customer, konsultasi, hingga kunjungan servis. Dibangun di atas `Appointments.jsx` (1015 baris) dengan tampilan kalender mingguan, tracking pembayaran, dan integrasi ke POS untuk pemilihan layanan.

Sistem ini dilindungi oleh `ERPAccessGuard` dengan `module="appointments"`, memastikan hanya user dengan akses yang tepat yang bisa mengelola jadwal.

## Arsitektur Komponen

```mermaid theme={null}
graph TD
    A[Appointments.jsx<br/>1015 lines] --> B[ERPAccessGuard<br/>module=appointments]
    A --> C[Stats Dashboard<br/>4 Metrics]
    A --> D[Week Calendar View]
    A --> E[AppointmentForm<br/>Modal]
    A --> F[AppointmentCard]
    A --> G[Filter Bar]
    
    C --> C1[Total Appointments]
    C --> C2[Pending]
    C --> C3[Completed Today]
    C --> C4[Total Revenue]
    
    D --> D1[Week Picker<br/>Date Navigation]
    D --> D2[7-Day Column Grid]
    D --> D3[Time Slot Labels]
    D --> D4[Appointment Blocks<br/>Color-coded]
    
    E --> H[Customer Selector<br/>CRM Integration]
    E --> I[Employee Selector<br/>HR Integration]
    E --> J[Service Selector<br/>POS Products]
    E --> K[Date & Time Picker]
    E --> L[Duration Selector<br/>7 options]
    
    F --> F1[Customer Info]
    F --> F2[Service & Price]
    F --> F3[Assigned Staff]
    F --> F4[Time & Duration]
    F --> F5[Status Badge]
    F --> F6[Payment Status]
    
    A --> M[base44.entities.Appointment]
    A --> N[base44.entities.Customer]
    A --> O[base44.entities.Employee]
    A --> P[base44.entities.CompanyPOSProduct]
```

## Diagram Relasi Entitas (ERD)

```mermaid theme={null}
erDiagram
    Company ||--o{ Appointment : "memiliki"
    Company ||--o{ Customer : "memiliki"
    Company ||--o{ Employee : "memiliki"
    Company ||--o{ CompanyPOSProduct : "memiliki"
    Company ||--o{ CustomerMembership : "mendefinisikan"

    Customer ||--o{ Appointment : "melakukan booking"
    Employee ||--o{ Appointment : "menangani"
    CompanyPOSProduct ||--o{ Appointment : "layanan yang dipilih"
    CustomerMembership ||--o{ Customer : "level keanggotaan"

    Appointment {
        string company_id FK
        string customer_id FK
        string customer_name "denormalized"
        string customer_email
        string customer_phone
        string service_id FK
        string service_name "denormalized"
        string assigned_to FK
        string assigned_to_name "denormalized"
        date appointment_date
        string appointment_time "HH:MM"
        number duration_minutes "default 60"
        string location
        string notes
        string status "enum 6 opsi"
        boolean reminder_sent "default false"
        string confirmation_code
        number price "default 0"
        string payment_status "enum 3 opsi"
        string cancellation_reason
    }

    Customer {
        string company_id FK
        string name
        string email
        string phone
        string whatsapp_number
        string company
        string address
        string customer_type "enum 6 opsi"
        string membership_level_id FK
        string membership_level_name
        string status "enum 4 opsi"
        string source
        array tags
        number lifetime_value
        number total_orders
        number average_order_value
        date last_purchase_date
        date birthday
    }

    Employee {
        string company_id FK
        string user_id FK
        string employee_id
        string full_name
        string email
        string phone
        string department "enum 8 opsi"
        string position
        string employment_type "enum 4 opsi"
        date hire_date
        number salary
        string status "enum 3 opsi"
        string avatar_url
    }

    CompanyPOSProduct {
        string company_id FK
        string name
        string sku
        string category
        number price
        number cost
        number stock
        number min_stock
        boolean is_active
        string product_type "enum 5 opsi"
        string unit
        number sold_count
    }

    CustomerMembership {
        string company_id FK
        string level_name
        string level_key
        string icon
        string color
        number discount_percentage
        number points_multiplier
        number min_purchase
        string scheme_type "enum 5 opsi"
        boolean is_active
    }

    Company {
        string name
        string owner_id FK
        string owner_email
        string industry "enum 8 opsi"
        string address
        string phone
        string email
        string tax_id
        number employee_count
    }
```

## Entity Schema Tables

### Entity: Appointment

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| `company_id` | string (FK → Company) | Ya | ID perusahaan pemilik appointment |
| `customer_id` | string (FK → Customer) | Tidak | ID customer yang melakukan booking |
| `customer_name` | string | Ya | Nama customer (denormalized untuk performa) |
| `customer_email` | string | Tidak | Alamat email customer |
| `customer_phone` | string | Ya | Nomor telepon customer untuk konfirmasi |
| `service_id` | string (FK → CompanyPOSProduct) | Tidak | ID produk/jasa yang di-booking dari katalog POS |
| `service_name` | string | Tidak | Nama layanan (denormalized dari CompanyPOSProduct) |
| `assigned_to` | string (FK → Employee) | Tidak | Email karyawan yang menangani appointment |
| `assigned_to_name` | string | Tidak | Nama lengkap karyawan (denormalized) |
| `appointment_date` | date | Ya | Tanggal appointment dijadwalkan |
| `appointment_time` | string (time) | Ya | Waktu mulai appointment, format HH:MM |
| `duration_minutes` | number | Tidak | Durasi appointment dalam menit (default: 60) |
| `location` | string | Tidak | Lokasi pelaksanaan appointment |
| `notes` | string | Tidak | Catatan tambahan untuk appointment |
| `status` | enum | Tidak | Status appointment: `pending`, `confirmed`, `rescheduled`, `completed`, `cancelled`, `no_show` (default: `pending`) |
| `reminder_sent` | boolean | Tidak | Flag apakah reminder sudah dikirim ke customer (default: `false`) |
| `confirmation_code` | string | Tidak | Kode konfirmasi unik untuk validasi customer |
| `price` | number | Tidak | Harga layanan (default: 0, diisi otomatis dari POS Product) |
| `payment_status` | enum | Tidak | Status pembayaran: `unpaid`, `paid`, `partially_paid` (default: `unpaid`) |
| `cancellation_reason` | string | Tidak | Alasan pembatalan (diisi saat status = `cancelled` atau `no_show`) |

### Entity: Customer (CRM Integration)

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| `company_id` | string (FK → Company) | Ya | ID perusahaan pemilik data customer |
| `name` | string | Ya | Nama lengkap customer |
| `email` | string | Tidak | Alamat email customer |
| `phone` | string | Ya | Nomor telepon utama |
| `whatsapp_number` | string | Tidak | Nomor WhatsApp untuk follow up |
| `company` | string | Tidak | Nama perusahaan customer (untuk B2B) |
| `address` | string | Tidak | Alamat lengkap customer |
| `customer_type` | enum | Tidak | Tipe customer: `individual`, `business`, `retail`, `reseller`, `distributor`, `modern_market` (default: `individual`) |
| `membership_level_id` | string (FK → CustomerMembership) | Tidak | ID level membership customer |
| `membership_level_name` | string | Tidak | Nama level membership (denormalized) |
| `membership_since` | date | Tidak | Tanggal sejak kapan menjadi member |
| `membership_points` | number | Tidak | Total poin member saat ini (default: 0) |
| `lifetime_points` | number | Tidak | Total poin sepanjang waktu (default: 0) |
| `stamps` | number | Tidak | Total stamp yang dimiliki saat ini (default: 0) |
| `status` | enum | Tidak | Status customer: `lead`, `prospect`, `customer`, `inactive` (default: `lead`) |
| `source` | string | Tidak | Sumber customer (ads, referral, walk-in, dll) |
| `tags` | array\[string] | Tidak | Tag-label untuk segmentasi |
| `lifetime_value` | number | Tidak | Total nilai transaksi sepanjang waktu (default: 0) |
| `total_orders` | number | Tidak | Jumlah seluruh transaksi (default: 0) |
| `average_order_value` | number | Tidak | Rata-rata nilai transaksi (default: 0) |
| `last_purchase_date` | date | Tidak | Tanggal pembelian terakhir |
| `birthday` | date | Tidak | Tanggal ulang tahun customer |

### Entity: Employee (HR Integration)

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| `company_id` | string (FK → Company) | Ya | ID perusahaan |
| `user_id` | string (FK → User) | Tidak | Link ke entitas User untuk autentikasi |
| `employee_id` | string | Ya | ID karyawan unik (NIP) |
| `full_name` | string | Ya | Nama lengkap karyawan |
| `email` | string | Ya | Alamat email karyawan |
| `phone` | string | Tidak | Nomor telepon karyawan |
| `department` | enum | Ya | Departemen: `Management`, `Sales`, `Marketing`, `Operations`, `Finance`, `IT`, `HR`, `Customer Service` |
| `position` | string | Ya | Jabatan / posisi karyawan |
| `description` | string | Tidak | Catatan atau deskripsi tambahan termasuk keahlian dan tanggung jawab (maks 1000 karakter) |
| `employment_type` | enum | Tidak | Tipe employment: `full_time`, `part_time`, `contract`, `intern` (default: `full_time`) |
| `hire_date` | date | Ya | Tanggal mulai bekerja |
| `salary` | number | Tidak | Gaji bulanan |
| `status` | enum | Tidak | Status karyawan: `active`, `on_leave`, `terminated` (default: `active`) |
| `avatar_url` | string | Tidak | URL foto profil karyawan |

### Entity: CompanyPOSProduct (POS Integration)

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| `company_id` | string (FK → Company) | Ya | ID perusahaan pemilik produk |
| `name` | string | Ya | Nama produk/jasa |
| `sku` | string | Tidak | SKU/Barcode produk |
| `category` | string | Tidak | Kategori produk |
| `price` | number | Ya | Harga jual layanan/produk |
| `cost` | number | Tidak | Harga modal/beli |
| `stock` | number | Tidak | Stok saat ini (default: 0) |
| `min_stock` | number | Tidak | Minimum stok untuk alert (default: 5) |
| `description` | string | Tidak | Deskripsi lengkap produk/jasa |
| `product_type` | enum | Tidak | Tipe produk: `finished_good`, `raw_material`, `semi_finished`, `packaging_material`, `bundle` (default: `finished_good`) |
| `is_active` | boolean | Tidak | Status aktif produk (default: `true`) |
| `unit` | string | Tidak | Satuan produk (default: `pcs`) |
| `sold_count` | number | Tidak | Total jumlah produk terjual (default: 0) |
| `tax_rate` | number | Tidak | Persentase pajak 0-100 (default: 0) |

### Entity: CustomerMembership (Loyalty Integration)

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| `company_id` | string (FK → Company) | Ya | ID perusahaan |
| `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 keuntungan dan syarat keanggotaan (maks 1000 karakter) |
| `discount_percentage` | number | Tidak | Diskon persentase untuk member level ini 0-100 (default: 0) |
| `points_multiplier` | number | Tidak | Multiplier poin yang didapat (default: 1) |
| `min_purchase` | number | Tidak | Minimum pembelian untuk mencapai level ini (default: 0) |
| `scheme_type` | enum | Tidak | Skema loyalty: `points`, `stamp`, `spending`, `visits`, `hybrid` (default: `points`) |
| `is_active` | boolean | Tidak | Status level aktif (default: `true`) |

## Status Appointment

```mermaid theme={null}
stateDiagram-v2
    [*] --> Pending: Appointment dibuat
    Pending --> Confirmed: Customer konfirmasi
    Pending --> Cancelled: Customer/staff batalkan
    Confirmed --> Completed: Appointment selesai
    Confirmed --> Rescheduled: Jadwal diubah
    Confirmed --> NoShow: Customer tidak hadir
    Confirmed --> Cancelled: Pembatalan mendadak
    Rescheduled --> Confirmed: Jadwal baru dikonfirmasi
    Rescheduled --> Cancelled: Dibatalkan setelah reschedule
    NoShow --> Confirmed: Customer datang terlambat
    Completed --> [*]
    Cancelled --> [*]
```

| Status | Warna | Ikon | Deskripsi |
| - | - | - | - |
| `pending` | Kuning | 🟡 | Menunggu konfirmasi dari customer |
| `confirmed` | Biru | 🔵 | Sudah dikonfirmasi, jadwal pasti |
| `completed` | Hijau | 🟢 | Appointment telah selesai dilaksanakan |
| `cancelled` | Merah | 🔴 | Dibatalkan oleh customer atau staff |
| `rescheduled` | Oranye | 🟠 | Dijadwalkan ulang ke waktu berbeda |
| `no_show` | Abu-abu | ⚪ | Customer tidak hadir tanpa kabar |

## Status Pembayaran

```mermaid theme={null}
stateDiagram-v2
    [*] --> Unpaid: Appointment dibuat
    Unpaid --> PartiallyPaid: Customer bayar sebagian
    Unpaid --> Paid: Customer bayar penuh
    PartiallyPaid --> Paid: Pelunasan
    Paid --> [*]
```

| Status | Warna | Deskripsi |
| - | - | - |
| `unpaid` | Merah | Belum ada pembayaran |
| `partially_paid` | Kuning | Sudah dibayar sebagian (DP) |
| `paid` | Hijau | Lunas, pembayaran penuh |

## Enum Reference Tables

### Enum: `Appointment.status`

| Nilai | Label | Deskripsi |
| - | - | - |
| `pending` | Pending | Appointment baru dibuat, menunggu konfirmasi customer |
| `confirmed` | Confirmed | Customer telah mengkonfirmasi jadwal |
| `rescheduled` | Rescheduled | Jadwal diubah ke waktu yang berbeda |
| `completed` | Completed | Appointment telah selesai dilaksanakan |
| `cancelled` | Cancelled | Appointment dibatalkan oleh customer atau staff |
| `no_show` | No Show | Customer tidak hadir tanpa pemberitahuan |

### Enum: `Appointment.payment_status`

| Nilai | Label | Deskripsi |
| - | - | - |
| `unpaid` | Unpaid | Belum ada pembayaran diterima |
| `partially_paid` | Partially Paid | Pembayaran diterima sebagian (DP) |
| `paid` | Paid | Pembayaran lunas dan penuh |

### Enum: `Customer.customer_type`

| Nilai | Label | Deskripsi |
| - | - | - |
| `individual` | Individual | Customer perorangan |
| `business` | Business | Customer perusahaan / B2B |
| `retail` | Retail | Pembeli eceran |
| `reseller` | Reseller | Penjual kembali |
| `distributor` | Distributor | Distributor produk |
| `modern_market` | Modern Market | Minimarket/supermarket |

### Enum: `Customer.status`

| Nilai | Label | Deskripsi |
| - | - | - |
| `lead` | Lead | Calon customer, belum melakukan transaksi |
| `prospect` | Prospect | Sudah menunjukkan minat, dalam proses konversi |
| `customer` | Customer | Customer aktif yang sudah bertransaksi |
| `inactive` | Inactive | Customer tidak aktif lagi |

### Enum: `Employee.department`

| Nilai | Label | Deskripsi |
| - | - | - |
| `Management` | Management | Tim manajemen / pimpinan |
| `Sales` | Sales | Tim penjualan |
| `Marketing` | Marketing | Tim pemasaran |
| `Operations` | Operations | Tim operasional |
| `Finance` | Finance | Tim keuangan |
| `IT` | IT | Tim teknologi informasi |
| `HR` | HR | Tim sumber daya manusia |
| `Customer Service` | Customer Service | Tim layanan pelanggan |

### Enum: `Employee.employment_type`

| Nilai | Label | Deskripsi |
| - | - | - |
| `full_time` | Full Time | Karyawan tetap penuh waktu |
| `part_time` | Part Time | Karyawan paruh waktu |
| `contract` | Contract | Karyawan kontrak / PKWT |
| `intern` | Intern | Magang |

### Enum: `Employee.status`

| Nilai | Label | Deskripsi |
| - | - | - |
| `active` | Active | Karyawan aktif bekerja |
| `on_leave` | On Leave | Sedang cuti / izin panjang |
| `terminated` | Terminated | Sudah tidak bekerja |

### Enum: `CompanyPOSProduct.product_type`

| Nilai | Label | Deskripsi |
| - | - | - |
| `finished_good` | Finished Good | Produk jadi siap jual |
| `raw_material` | Raw Material | Bahan baku |
| `semi_finished` | Semi Finished | Produk setengah jadi |
| `packaging_material` | Packaging Material | Bahan kemasan |
| `bundle` | Bundle | Paket bundel virtual |

### Enum: `CustomerMembership.scheme_type`

| Nilai | Label | Deskripsi |
| - | - | - |
| `points` | Points | Akumulasi poin dari transaksi |
| `stamp` | Stamp | Koleksi stamp per transaksi |
| `spending` | Spending | Berdasarkan total belanja |
| `visits` | Visits | Berdasarkan frekuensi kunjungan |
| `hybrid` | Hybrid | Kombinasi beberapa skema |

## Duration Options

| Durasi | Ikon | Kegunaan | Contoh Layanan |
| - | - | - | - |
| 15 menit | ⚡ | Konsultasi singkat | Quick consultation, follow-up |
| 30 menit | ⏱️ | Servis standar | Potong rambut, facial dasar |
| 45 menit | 🕐 | Servis menengah | Hair coloring, manicure |
| 60 menit | 🕐 | Servis penuh | Full body massage, deep facial |
| 90 menit | 🕑 | Treatment panjang | Spa package, hair treatment |
| 120 menit | 🕒 | Paket lengkap | Full spa day, bridal prep |
| 180 menit | 🕓 | Session khusus | Extended treatment, VIP package |

## Waktu Selesai Otomatis

Fungsi `calcEndTime()` menghitung waktu selesai berdasarkan waktu mulai dan durasi:

```mermaid theme={null}
flowchart LR
    A[appointment_time<br/>10:00] --> C[calcEndTime]
    B[duration_minutes<br/>60] --> C
    C --> D[endTime<br/>11:00]
    
    E[appointment_time<br/>14:30] --> F[calcEndTime]
    G[duration_minutes<br/>90] --> F
    F --> H[endTime<br/>16:00]
```

```
endTime = appointment_time + duration_minutes
```

Ini ditampilkan di AppointmentCard supaya staff tahu kapan appointment berakhir dan bisa mempersiapkan sesi berikutnya.

## Revenue Tracking

```mermaid theme={null}
flowchart TD
    A[All Appointments] --> B{status = completed?}
    B -->|Ya| C{payment_status = paid?}
    B -->|Tidak| D[Skip dari revenue]
    C -->|Ya| E[Sum price]
    C -->|Tidak| F[Sum price<br/>termasuk unpaid]
    E --> G[Total Revenue]
    F --> G
```

Stats dashboard menghitung 4 metrik utama:

| Metrik | Perhitungan | Deskripsi |
| - | - | - |
| **Total Appointments** | `appointments.length` | Jumlah seluruh appointment |
| **Pending** | `filter(status === 'pending')` | Menunggu konfirmasi |
| **Completed Today** | `filter(status === 'completed' && date === today)` | Selesai hari ini |
| **Total Revenue** | `Σ price where status === 'completed'` | Revenue dari appointment selesai |

## Week Calendar View

```mermaid theme={null}
graph LR
    subgraph "7-Day Window"
        A[Senin<br/>13 Okt] --- B[Selasa<br/>14 Okt] --- C[Rabu<br/>15 Okt] --- D[Kamis<br/>16 Okt] --- E[Jumat<br/>17 Okt] --- F[Sabtu<br/>18 Okt] --- G[Minggu<br/>19 Okt]
    end
    H[Week Picker<br/>◀ Minggu Ini ▶] --> A
    H --> G
    
    subgraph "Per Day Column"
        I[08:00] --- J[09:00<br/>Appointment A<br/>🟡 Pending] --- K[10:00<br/>Appointment B<br/>🔵 Confirmed] --- L[11:00] --- M[...<br/>...]
    end
```

Tampilan kalender menampilkan 7 hari ke depan dari tanggal yang dipilih. Setiap slot menampilkan appointment dengan warna sesuai status. Week picker memungkinkan navigasi ke minggu-minggu sebelumnya atau berikutnya.

## Integrasi Lintas Modul

| Modul | Entity | Fungsi | Data Flow |
| - | - | - | - |
| CRM | Customer | Data customer untuk booking | Customer → Appointment (customer\_id, name, phone, email) |
| HR | Employee | Assignment staff ke appointment | Employee → Appointment (assigned\_to, assigned\_to\_name) |
| POS | CompanyPOSProduct | Katalog layanan dari produk POS | POSProduct → Appointment (service\_id, service\_name, price) |
| CRM | CustomerMembership | Level membership dan diskon | CustomerMembership → Customer (level, discount, points) |
| Access Control | ERPAccessGuard | Module-level permission | Guard → Appointments (access check) |
| Company | Company | Scope multi-tenant | Company → semua entitas (company\_id) |

```mermaid theme={null}
flowchart LR
    subgraph "Modul CRM"
        CUS[Customer]
        CM[CustomerMembership]
    end

    subgraph "Modul HR"
        EMP[Employee]
    end

    subgraph "Modul POS"
        POS[CompanyPOSProduct]
    end

    subgraph "Modul Productivity"
        APT[Appointment]
    end

    CUS -->|customer_id, name, phone, email| APT
    EMP -->|assigned_to, assigned_to_name| APT
    POS -->|service_id, service_name, price| APT
    CM -->|membership_level_id, level_name| CUS
```

## Sequence Diagrams

### Flow Pembuatan Appointment Baru

```mermaid theme={null}
sequenceDiagram
    participant U as Staff
    participant A as Appointments
    participant CRM as Customer Entity
    participant POS as POS Products
    participant HR as Employee Entity
    participant DB as Base44

    U->>A: Klik "Tambah Appointment"
    A->>CRM: Fetch customer list
    CRM-->>A: Customers
    U->>A: Pilih customer
    A->>POS: Fetch products/services
    POS-->>A: Services + prices
    U->>A: Pilih layanan, tanggal, waktu, durasi
    A->>HR: Fetch employee list
    HR-->>A: Employees
    U->>A: Assign staff, isi lokasi & catatan
    U->>A: Klik Simpan
    A->>DB: Create Appointment
    Note over DB: status = 'pending'<br/>payment_status = 'unpaid'
    DB-->>A: Appointment created
    A->>A: Refresh calendar view
```

### Flow Notifikasi dan Reminder

```mermaid theme={null}
sequenceDiagram
    participant CRON as Scheduler
    participant A as Appointment Service
    participant WA as WhatsApp Gateway
    participant CUS as Customer
    participant DB as Base44

    CRON->>A: Cek appointment hari ini
    A->>DB: Query appointments where reminder_sent = false
    DB-->>A: List appointment pending reminder
    loop Untuk setiap appointment
        A->>A: Hitung waktu kirim (H-1 atau H-2)
        alt appointment_date - today <= threshold
            A->>WA: Kirim reminder via WhatsApp
            WA-->>CUS: "Reminder: Appointment Anda besok jam XX:XX"
            CUS-->>WA: Konfirmasi / Reschedule
            WA-->>A: Update status
            A->>DB: Set reminder_sent = true
        else belum waktunya
            A->>A: Skip, cek lagi nanti
        end
    end
```

### Flow Reschedule Appointment

```mermaid theme={null}
sequenceDiagram
    participant U as Staff
    participant A as Appointments
    participant DB as Base44
    participant WA as WhatsApp Gateway
    participant CUS as Customer

    U->>A: Buka appointment detail
    U->>A: Klik "Reschedule"
    A->>A: Tampilkan form tanggal & waktu baru
    U->>A: Pilih tanggal/waktu baru
    U->>A: Klik "Simpan Reschedule"
    A->>DB: Update status = 'rescheduled'
    DB-->>A: Updated
    A->>WA: Kirim notifikasi reschedule
    WA-->>CUS: "Jadwal appointment Anda diubah ke [tanggal baru]"
    CUS-->>WA: Konfirmasi jadwal baru
    A->>DB: Update status = 'confirmed', tanggal baru
    DB-->>A: Confirmed
```

### Flow Pembayaran Appointment

```mermaid theme={null}
sequenceDiagram
    participant U as Staff/Kasir
    participant A as Appointments
    participant POS as POS Transaction
    participant DB as Base44

    U->>A: Buka appointment completed
    U->>A: Klik "Proses Pembayaran"
    alt Bayar penuh
        U->>A: Set payment_status = 'paid'
        A->>POS: Buat transaksi POS
        POS-->>DB: Record transaction
    else Bayar sebagian (DP)
        U->>A: Set payment_status = 'partially_paid'
        A->>DB: Update payment_status
    end
    A->>DB: Save payment update
    DB-->>A: Payment recorded
    A->>A: Update revenue dashboard
```

## Flow Penggunaan Detail

1. Buka halaman Appointments dari sidebar — lihat stats revenue dan jumlah appointment di atas
2. Gunakan **week picker** untuk navigasi ke minggu yang diinginkan
3. Klik slot waktu kosong atau tombol **"Tambah Appointment"** untuk booking baru
4. Pilih customer dari daftar, atau buat customer baru jika belum ada
5. Pilih layanan dari katalog POS Products — harga otomatis terisi
6. Tentukan tanggal, waktu, durasi, dan assign ke staff yang tersedia
7. Isi lokasi dan catatan tambahan jika perlu
8. Klik **Simpan** — status awal adalah `pending`
9. Update status ke `confirmed` saat customer mengkonfirmasi
10. Setelah appointment selesai, update ke `completed` dan set payment status

## Filter & Pencarian

| Filter | Opsi | Deskripsi |
| - | - | - |
| **Status** | All, Pending, Confirmed, Completed, Cancelled, Rescheduled, No Show | Filter berdasarkan status appointment |
| **Date** | Week picker — pilih minggu yang diinginkan | Navigasi per minggu |
| **Search** | Pencarian berdasarkan nama customer | Full-text search di customer\_name |

## RBAC Permission Table

Sistem appointment menggunakan `ERPAccessGuard` dengan `module="appointments"` dan Row-Level Security (RLS) dari Base44. Berikut adalah matriks hak akses berdasarkan peran:

| Operasi | Admin | Owner Company | Staff (creator) | User Lain |
| - | - | - | - | - |
| **Create** | Ya (semua data) | Ya (scoped ke `active_company_id`) | Ya (hanya buat baru) | Tidak |
| **Read** | Ya (semua data) | Ya (scoped ke `active_company_id`) | Ya (hanya data sendiri) | Tidak |
| **Update** | Ya (semua data) | Ya (scoped ke `active_company_id`) | Ya (hanya data sendiri) | Tidak |
| **Delete** | Ya (semua data) | Ya (scoped ke `active_company_id`) | Ya (hanya data sendiri) | Tidak |

### RLS Conditions (dari JSONC)

```
create/read/update/delete:
  $or:
    - $and:
        - data.company_id == user.data.active_company_id
        - data.company_id NOT IN [null, ""]
    - created_by_id == user.id
    - user.role == "admin"
```

**Penjelasan:**

* **Admin** (`role = "admin"`) memiliki akses penuh ke seluruh appointment tanpa batasan company
* **Owner Company** dapat mengakses appointment selama `company_id` sesuai dengan `active_company_id` yang sedang aktif
* **Staff** (creator) hanya dapat mengakses appointment yang mereka buat sendiri (`created_by_id == user.id`)
* **User lain** yang tidak memenuhi kondisi di atas tidak memiliki akses sama sekali

### Akses Guard pada Komponen

```jsx theme={null}
<ERPAccessGuard module="appointments">
  <AppointmentsContent />
</ERPAccessGuard>
```

Guard ini memastikan hanya user yang memiliki modul `appointments` aktif di paket ERP mereka yang bisa mengakses halaman ini.

## Tips

* **Selalu gunakan durasi standar** dari dropdown supaya `calcEndTime` bisa menghitung waktu selesai secara akurat
* **Cek kalender setiap pagi** untuk mengetahui jadwal hari ini dan antisipasi konflik jadwal
* **Set `reminder_sent = true`** setelah mengirim reminder ke customer — ini mencegah reminder ganda
* **Untuk customer yang tidak hadir**, gunakan status `no_show` dan isi `cancellation_reason` untuk tracking
* **Manfaatkan integrasi POS Products** supaya harga layanan konsisten dengan yang ada di kasir
* **Denormalized fields** (`customer_name`, `assigned_to_name`, `service_name`) membuat card tampil cepat tanpa perlu join ke entity lain
* **Review appointment cancelled/no-show** secara berkala untuk mengidentifikasi pola dan mengurangi kerugian
* **Gunakan `confirmation_code`** untuk verifikasi customer saat check-in, terutama untuk appointment bernilai tinggi
* **Manfaatkan `CustomerMembership`** untuk memberikan diskon otomatis berdasarkan level membership customer
* **Perhatikan `Employee.status`** — hanya assign ke karyawan dengan status `active` untuk menghindari penugasan ke karyawan yang sudah resign atau cuti panjang

## Cara Akses

Dari sidebar, klik menu **Productivity** > **Appointments**.


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