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

# Rbac

<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: "RBAC & User Management"
description: "Role-Based Access Control: RoleManager 1398 baris, 13 role templates, 38 permissions, 3 SoD rules, ERPAccessGuard, MenuAccessProfile, dan real-time sync."
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

# RBAC & User Management

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/core/admin-dashboard.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=286e13d72bced05a5ab6d6cc3a03cfb4" alt="RBAC" width="1920" height="1080" data-path="docs/mintlify/screenshots/core/admin-dashboard.png" />

Quinn of Spicy menggunakan Role-Based Access Control (RBAC) yang sophisticated untuk mengontrol akses user berdasarkan peran dan tanggung jawab masing-masing. RoleManager.jsx (1398 baris) adalah komponen inti yang mengelola member invitation, role assignment, dan permission matrix dengan 13 role templates dan 38 granular permissions.

Sistem ini juga mengimplementasikan Segregation of Duties (SoD) — 3 aturan kritis yang mencegah conflict of interest: stock opname counter tidak bisa jadi approver, production operator tidak bisa jadi QC approver, dan CS tidak bisa verifikasi transaksi tunai.

Permission resolution menggunakan `useCompanyRole.jsx` hook (524 baris) dengan aggressive caching (60-second TTL), deduplication of in-flight fetches, dan real-time cache invalidation via BroadcastChannel.

Sistem RBAC Quinn of Spicy dibangun di atas lima entitas inti yang saling terhubung: **User**, **Company**, **CompanyMember**, **CompanyInvitation**, dan **MenuAccessProfile**. Kelima entitas ini membentuk fondasi kontrol akses multi-tenant yang memastikan setiap pengguna hanya dapat mengakses fitur, menu, dan data sesuai dengan peran dan izin yang diberikan.

***

## Arsitektur Komponen

```mermaid theme={null}
graph TD
    A[RBAC System] --> B[RoleManager.jsx<br/>1398 lines]
    A --> C[CompanyAccessControl.jsx<br/>273 lines]
    A --> D[useCompanyRole.jsx<br/>524 lines]
    A --> E[ERPAccessGuard.jsx<br/>569 lines]
    
    B --> F[13 Role Templates]
    B --> G[38 Permissions]
    B --> H[Member Invitation]
    B --> I[Idempotency Keys]
    
    C --> J[3 Tabs<br/>Members / Audit / SoD]
    C --> K[3 SoD Rules]
    
    D --> L[60s Cache TTL]
    D --> M[10s Auto-refresh]
    D --> N[BroadcastChannel Invalidation]
    
    E --> O[Permission Check]
    E --> P[3-Day Grace Period]
    E --> Q[Read-Only Mode]
```

### Ringkasan Alur Kontrol Akses

Sistem RBAC bekerja dalam tiga lapisan utama:

1. **Lapisan Autentikasi** — User login dan sistem memverifikasi identitas melalui entity `User`. Setiap user memiliki `admin_type` dan `admin_tier` yang menentukan hak akses di level platform.
2. **Lapisan Keanggotaan Company** — Entity `CompanyMember` menghubungkan user ke company tertentu dengan role dan permissions. Satu user bisa menjadi member di beberapa company sekaligus.
3. **Lapisan Akses Menu** — Entity `MenuAccessProfile` memetakan role ke daftar menu yang boleh ditampilkan, sehingga sidebar navigasi otomatis menyesuaikan dengan hak akses pengguna.

***

## Entity Relationship Diagram

```mermaid theme={null}
erDiagram
    User ||--o{ CompanyMember : "memiliki keanggotaan di"
    User ||--o{ Company : "membuat/memiliki"
    User ||--o{ CompanyInvitation : "mengirim undangan"
    Company ||--o{ CompanyMember : "memiliki anggota"
    Company ||--o{ CompanyInvitation : "memiliki undangan"
    Company ||--o{ MenuAccessProfile : "memiliki profil akses"
    Company ||--o{ MenuSettings : "memiliki konfigurasi menu"
    CompanyMember }o--|| MenuAccessProfile : "menggunakan profil"

    User {
        string id PK
        string email UK "Email pengguna"
        string full_name "Nama lengkap"
        string role "admin | user"
        string admin_type "owner | basic"
        string admin_tier "none | business | advanced | enterprise"
        string subscription_plan "free | pro | business | advanced | enterprise"
        string active_company_id FK "Company yang sedang aktif"
    }

    Company {
        string id PK
        string name "Nama perusahaan"
        string owner_id FK "ID pemilik perusahaan"
        string owner_email "Email pemilik perusahaan"
        string industry "Sektor bisnis"
        string owner_subscription_plan "Plan membership owner"
    }

    CompanyMember {
        string id PK
        string company_id FK "ID perusahaan"
        string user_id FK "ID pengguna"
        string user_email "Email pengguna"
        string role "Peran dalam perusahaan"
        object permissions "38 flag hak akses"
        string status "active | inactive | pending"
        date joined_date "Tanggal bergabung"
    }

    CompanyInvitation {
        string id PK
        string company_id FK "ID perusahaan"
        string invited_email "Email yang diundang"
        string invited_by "Email pengundang"
        string role "Peran yang ditawarkan"
        object permissions "Hak akses awal"
        string status "pending | accepted | rejected | expired | cancelled"
    }

    MenuAccessProfile {
        string id PK
        string company_id FK "ID perusahaan"
        string role "Peran yang menggunakan profil"
        string profile_name "Nama profil akses"
        array allowed_menus "Daftar ID menu yang diizinkan"
        string dashboard_type "Tipe dashboard"
        boolean is_default "Apakah profil default"
    }

    MenuSettings {
        string id PK
        string menu_id UK "ID unik menu"
        string menu_name "Nama tampilan menu"
        string menu_url "URL tujuan"
        string parent_menu "Menu induk"
        boolean is_active "Status aktif"
    }
```

***

## Entity Schema Tables

### User

Entitas `User` merepresentasikan akun pengguna platform. Setiap pengguna memiliki identitas unik, preferensi subscription, dan hak akses administratif di level platform (berbeda dengan hak akses di level company).

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | Ya | Auto-generated | Primary key unik untuk setiap user |
| `email` | string | **Ya** | — | Alamat email pengguna (unique) |
| `full_name` | string | **Ya** | — | Nama lengkap pengguna |
| `role` | enum | Tidak | — | Role di level aplikasi: `admin` atau `user` |
| `admin_type` | enum | Tidak | — | Tipe admin platform: `owner` (full access aplikasi) atau `basic` (hanya transaksi produk digital). **Berbeda** dengan company owner |
| `admin_tier` | enum | Tidak | `none` | Tier admin untuk company management: `none`, `business`, `advanced`, `enterprise` |
| `subscription_plan` | enum | Tidak | `free` | Plan membership aktif: `free`, `pro`, `business`, `advanced`, `enterprise` |
| `subscription_start` | date-time | Tidak | — | Tanggal mulai subscription aktif |
| `subscription_end` | date-time | Tidak | — | Tanggal berakhir subscription aktif |
| `membership_duration_type` | enum | Tidak | — | Durasi membership: `monthly`, `yearly`, `custom`, `lifetime` |
| `membership_start_date` | date | Tidak | — | Tanggal mulai membership |
| `membership_end_date` | date | Tidak | — | Tanggal berakhir membership |
| `is_readonly_mode` | boolean | Tidak | `false` | Mode read-only saat expired dalam grace period 3 hari |
| `active_company_id` | string | Tidak | — | ID company yang sedang aktif untuk Business+ users |
| `company_slots_purchased` | number | Tidak | `0` | Slot company tambahan yang dibeli di luar limit plan |
| `balance` | number | Tidak | `0` | Saldo deposit untuk membeli membership/addon |
| `commission_balance` | number | Tidak | `0` | Saldo komisi referral yang dapat ditarik |
| `admin_commission_balance` | number | Tidak | `0` | Saldo komisi admin basic dari proses transaksi |
| `total_earnings` | number | Tidak | `0` | Total komisi yang pernah diterima (akumulasi) |
| `productivity_score` | number | Tidak | `0` | Skor produktivitas pengguna |
| `user_level` | number | Tidak | `1` | Level pengguna berdasarkan achievement points |
| `preferences` | object | Tidak | — | Preferensi UI: `theme`, `primary_color`, `wallpaper_url`, `referral_modal_dismissed` |

### Company

Entitas `Company` merepresentasikan perusahaan atau organisasi yang menggunakan sistem ERP. Setiap company memiliki owner, konfigurasi modul aktif, dan pengaturan operasional.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | Ya | Auto-generated | Primary key unik untuk setiap company |
| `name` | string | **Ya** | — | Nama perusahaan |
| `owner_id` | string | **Ya** | — | ID user yang menjadi pemilik company |
| `owner_email` | string | **Ya** | — | Email pemilik company |
| `owner_subscription_plan` | enum | Tidak | — | Plan membership owner: `free`, `pro`, `business`, `advanced`, `enterprise` |
| `description` | string | Tidak | — | Deskripsi perusahaan |
| `industry` | enum | Tidak | — | Sektor industri: `retail`, `manufacturing`, `services`, `technology`, `food_beverage`, `healthcare`, `education`, `other` |
| `address` | string | Tidak | — | Alamat fisik perusahaan |
| `phone` | string | Tidak | — | Nomor telepon perusahaan |
| `email` | string | Tidak | — | Alamat email perusahaan |
| `website` | string | Tidak | — | URL website perusahaan |
| `logo_url` | string | Tidak | — | URL logo perusahaan |
| `tax_id` | string | Tidak | — | NPWP perusahaan |
| `employee_count` | number | Tidak | `0` | Jumlah karyawan |
| `business_type` | string | Tidak | — | Kategori bisnis yang dipilih saat onboarding |
| `active_modules` | string | Tidak | — | JSON string berisi array ID modul yang aktif |
| `metadata` | object | Tidak | — | Data tambahan / legacy |
| `landing_page_config` | object | Tidak | — | Konfigurasi landing page perusahaan |
| `settings` | object | Tidak | — | Pengaturan operasional: jam kerja, kebijakan cuti, threshold expiry, strategi alokasi batch, konfigurasi pajak, perlakuan spoilage |

### CompanyMember

Entitas `CompanyMember` adalah jantung dari sistem RBAC — menghubungkan user ke company dengan role dan 38 permission flags yang sangat granular.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | Ya | Auto-generated | Primary key unik |
| `company_id` | string | **Ya** | — | ID company tempat user menjadi member |
| `user_id` | string | Tidak | — | ID user yang menjadi member |
| `user_email` | string | **Ya** | — | Email user yang menjadi member |
| `user_name` | string | Tidak | — | Nama lengkap user |
| `role` | enum | Tidak | `employee` | Role dalam perusahaan (lihat tabel role di bawah) |
| `employee_id` | string | Tidak | — | Link ke entitas Employee untuk integrasi HR |
| `department` | string | Tidak | — | Departemen tempat member bekerja |
| `position` | string | Tidak | — | Jabatan/posisi member |
| `status` | enum | Tidak | `active` | Status keanggotaan: `active`, `inactive`, `pending` |
| `joined_date` | date | Tidak | — | Tanggal bergabung ke company |
| `invited_by` | string | Tidak | — | Email user yang mengundang member ini |
| `permissions` | object | Tidak | — | 38 flag hak akses detail (lihat tabel lengkap di bawah) |
| `assigned_locations` | array\[string] | Tidak | — | Daftar ID lokasi gudang/outlet yang diizinkan (RBAC-01) |
| `working_hours` | object | Tidak | — | Jam kerja: `start` (default `09:00`) dan `end` (default `17:00`) |
| `salary` | number | Tidak | — | Gaji karyawan (opsional) |
| `notes` | string | Tidak | — | Catatan tambahan mengenai member |

### CompanyInvitation

Entitas `CompanyInvitation` merepresentasikan undangan keanggotaan yang dikirimkan kepada user yang belum bergabung. Invitation memiliki lifecycle status yang jelas dan akan expired setelah waktu yang ditentukan.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | Ya | Auto-generated | Primary key unik |
| `company_id` | string | **Ya** | — | ID company yang mengirim undangan |
| `company_name` | string | Tidak | — | Nama company (untuk tampilan) |
| `invited_email` | string | **Ya** | — | Email calon member yang diundang |
| `invited_by` | string | **Ya** | — | Email user yang mengirim undangan |
| `invited_by_name` | string | Tidak | — | Nama pengundang |
| `role` | enum | Tidak | `employee` | Role yang ditawarkan: `owner`, `admin`, `supervisor`, `store_admin`, `stock_admin`, `finance_admin`, `hr_admin`, `transaction_admin`, `employee` |
| `department` | string | Tidak | — | Departemen yang ditawarkan |
| `position` | string | Tidak | — | Jabatan yang ditawarkan |
| `description` | string | Tidak | — | Pesan tambahan dalam undangan (maks. 1000 karakter) |
| `permissions` | object | Tidak | — | Hak akses awal yang ditawarkan (24 flag dasar) |
| `message` | string | Tidak | — | Pesan personal untuk invitee |
| `status` | enum | Tidak | `pending` | Status undangan: `pending`, `accepted`, `rejected`, `expired`, `cancelled` |
| `expires_at` | date-time | Tidak | — | Waktu kedaluwarsa undangan |
| `accepted_at` | date-time | Tidak | — | Timestamp saat undangan diterima |
| `rejected_at` | date-time | Tidak | — | Timestamp saat undangan ditolak |
| `cancelled_at` | date-time | Tidak | — | Timestamp saat undangan dibatalkan |

### MenuAccessProfile

Entitas `MenuAccessProfile` mendefinisikan profil akses menu per role — menentukan menu/sidebar mana saja yang boleh ditampilkan untuk setiap role dalam suatu company.

| Field | Tipe | Required | Default | Deskripsi |
| - | - | - | - | - |
| `id` | string (UUID) | Ya | Auto-generated | Primary key unik |
| `company_id` | string | **Ya** | — | ID perusahaan (isolasi data per company) |
| `role` | enum | **Ya** | — | Role yang menggunakan profil: `owner`, `admin`, `supervisor`, `store_admin`, `stock_admin`, `finance_admin`, `hr_admin`, `transaction_admin`, `employee` |
| `profile_name` | string | **Ya** | — | Nama profil akses menu |
| `description` | string | Tidak | — | Penjelasan hak akses menu (maks. 1000 karakter) |
| `allowed_menus` | array\[string] | **Ya** | — | Daftar ID menu yang diizinkan untuk role ini |
| `dashboard_type` | enum | Tidak | `employee` | Tipe tampilan dashboard: `owner`, `admin`, `supervisor`, `employee`, `cashier`, `finance`, `hr`, `inventory` |
| `is_default` | boolean | Tidak | `true` | Apakah ini profil default untuk role ini |
| `created_by` | string | Tidak | — | Email owner yang membuat profil |

***

## 13 Role Templates

Sistem RBAC Quinn of Spicy mendukung 13 role templates yang dirancang untuk mencakup berbagai fungsi operasional dalam perusahaan ERP. Setiap role memiliki set permission default yang dapat dikustomisasi lebih lanjut.

| Role | Emoji | Akses Utama | Key Permissions |
| - | - | - | - |
| **Owner** | 👑 | Full access | Semua permissions — bypass semua check |
| **Admin** | 🛡️ | Operational + reporting | Semua kecuali settings edit dan role management |
| **Supervisor** | 👁️ | Read-only oversight | Read-only finance/inventory/HR |
| **Store Admin** | 🏪 | Toko & outlet | POS management, shift cashier, store settings |
| **Stock Admin** | 📦 | Warehouse ops | Stock management, movements, barcode, suppliers |
| **Finance Admin** | 💳 | Accounting | Invoices, expenses, bank accounts, GL, reports |
| **HR Admin** | 👥 | Human resources | Employees, attendance, payroll, leave management |
| **Transaction Admin** | 🧾 | Transaksi | POS create orders, process payments, view products |
| **Employee** | 👤 | Basic access | Dashboard, tasks, notes — akses dasar |
| **Production Operator** | 🏭 | Manufacturing | Production orders, BOM view, batch creation |
| **QC Inspector** | 🔬 | Quality control | QC records, batch verification, release approval |
| **Sales & Marketing** | 📈 | Sales channel | Channel pricing, distribution, B2B invoices |
| **Partner/Distributor** | 🤝 | B2B partnership | Distribution shipment, B2B invoice view |

***

## 38 Permission Flags — Matriks Lengkap

Berikut adalah seluruh 38 permission flags yang tersedia dalam `CompanyMember.permissions` dan `CompanyInvitation.permissions`. Setiap flag bertipe `boolean` dan dapat diaktifkan/nonaktifkan secara independen.

### Dashboard & Tugas

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_dashboard` | boolean | `true` | Akses untuk melihat dashboard utama company |
| `can_view_tasks` | boolean | `true` | Akses untuk melihat daftar tugas |
| `can_create_tasks` | boolean | `true` | Akses untuk membuat tugas baru |
| `can_edit_tasks` | boolean | `true` | Akses untuk mengedit tugas yang ada |
| `can_delete_tasks` | boolean | `false` | Akses untuk menghapus tugas |

### Catatan (Notes)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_notes` | boolean | `true` | Akses untuk melihat catatan |
| `can_create_notes` | boolean | `true` | Akses untuk membuat catatan baru |
| `can_edit_notes` | boolean | `true` | Akses untuk mengedit catatan |
| `can_delete_notes` | boolean | `false` | Akses untuk menghapus catatan |

### HR & Payroll

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_hr` | boolean | `false` | Akses untuk melihat data karyawan, absensi, dan payroll |
| `can_edit_hr` | boolean | `false` | Akses untuk mengedit data karyawan, memproses absensi dan payroll |

### Keuangan (Finance)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_finance` | boolean | `false` | Akses untuk melihat invoice, expense, bank account, dan laporan keuangan |
| `can_edit_finance` | boolean | `false` | Akses untuk membuat/mengedit transaksi keuangan, expense, dan journal entry |

### Inventaris (Inventory)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_inventory` | boolean | `false` | Akses untuk melihat stok, lokasi gudang, dan pergerakan inventaris |
| `can_edit_inventory` | boolean | `false` | Akses untuk mengelola stok, membuat pergerakan, dan mengedit data inventaris |
| `can_transfer_inventory` | boolean | `false` | Akses untuk melakukan transfer stok antar gudang/outlet |
| `can_approve_stock_opname` | boolean | `false` | Akses untuk menyetujui hasil stock opname (**SoD**: tidak boleh sama dengan `can_count_stock_opname`) |
| `can_count_stock_opname` | boolean | `false` | Akses untuk menghitung stok saat stock opname (**SoD**: tidak boleh sama dengan `can_approve_stock_opname`) |

### Proyek (Projects)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_projects` | boolean | `false` | Akses untuk melihat daftar dan detail proyek |
| `can_edit_projects` | boolean | `false` | Akses untuk membuat dan mengedit proyek |

### POS / Kasir

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_pos` | boolean | `false` | Akses untuk melihat halaman POS dan transaksi |
| `can_use_pos` | boolean | `false` | Akses untuk menggunakan POS: membuat order, memproses pembayaran |
| `can_manage_cashier_shift` | boolean | `false` | Akses untuk mengelola shift kasir: buka/tutup shift, setoran |

### Laporan (Reports)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_reports` | boolean | `false` | Akses untuk melihat laporan analitik dan reporting |

### Manajemen User & Settings

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_manage_members` | boolean | `false` | Akses untuk mengundang, mengedit, dan menghapus member company |
| `can_manage_roles` | boolean | `false` | Akses untuk mengelola role dan permission templates |
| `can_view_settings` | boolean | `false` | Akses untuk melihat halaman pengaturan company |
| `can_edit_settings` | boolean | `false` | Akses untuk mengubah pengaturan company |

### Produksi (Manufacturing)

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_create_production_batch` | boolean | `false` | Akses untuk membuat batch produksi baru |
| `can_release_production_qc` | boolean | `false` | Akses untuk merilis batch setelah QC (**SoD**: tidak boleh sama dengan `can_create_production_batch`) |
| `can_view_hpp` | boolean | `false` | Akses untuk melihat perhitungan HPP (Harga Pokok Produksi) |

### Distribusi & Sales Channel

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_manage_channel_pricing` | boolean | `false` | Akses untuk mengelola harga per sales channel |
| `can_view_distribution` | boolean | `false` | Akses untuk melihat data distribusi dan shipment |
| `can_create_distribution_shipment` | boolean | `false` | Akses untuk membuat shipment distribusi baru |
| `can_confirm_distribution_shipment` | boolean | `false` | Akses untuk mengkonfirmasi penerimaan shipment distribusi |

### B2B & Invoice

| Permission | Tipe | Default | Deskripsi |
| - | - | - | - |
| `can_view_b2b_invoices` | boolean | `false` | Akses untuk melihat invoice B2B |
| `can_create_b2b_invoice` | boolean | `false` | Akses untuk membuat invoice B2B baru |
| `can_verify_b2b_payment` | boolean | `false` | Akses untuk memverifikasi pembayaran B2B (**SoD**: tidak boleh dipegang oleh CS Agent) |

***

## Enum Reference Tables

### CompanyMember.role

| Nilai | Deskripsi |
| - | - |
| `owner` | Pemilik company — akses penuh tanpa batasan |
| `admin` | Administrator operasional — akses luas kecuali ownership |
| `supervisor` | Pengawas — akses read-only ke sebagian besar modul |
| `store_admin` | Administrator toko — mengelola POS dan outlet |
| `stock_admin` | Administrator gudang — mengelola inventaris |
| `finance_admin` | Administrator keuangan — mengelola invoice, expense, GL |
| `hr_admin` | Administrator HR — mengelola karyawan, absensi, payroll |
| `transaction_admin` | Administrator transaksi — mengelola order POS dan pembayaran |
| `employee` | Karyawan biasa — akses dasar dashboard dan tugas |
| `production_operator` | Operator produksi — mengelola batch produksi |
| `qc_inspector` | Inspector QC — verifikasi kualitas dan rilis batch |
| `sales_marketing` | Sales & marketing — mengelola pricing channel dan distribusi |
| `partner_distributor` | Partner/distributor — akses terbatas untuk B2B |

### CompanyInvitation.role

| Nilai | Deskripsi |
| - | - |
| `owner` | Pemilik company |
| `admin` | Administrator operasional |
| `supervisor` | Pengawas read-only |
| `store_admin` | Administrator toko |
| `stock_admin` | Administrator gudang |
| `finance_admin` | Administrator keuangan |
| `hr_admin` | Administrator HR |
| `transaction_admin` | Administrator transaksi |
| `employee` | Karyawan biasa (default) |

### CompanyMember.status

| Nilai | Deskripsi |
| - | - |
| `active` | Member aktif — memiliki akses penuh sesuai permissions |
| `inactive` | Member nonaktif — akses ditolak |
| `pending` | Member menunggu konfirmasi — akses terbatas |

### CompanyInvitation.status

| Nilai | Deskripsi |
| - | - |
| `pending` | Undangan belum direspon |
| `accepted` | Undangan diterima — CompanyMember dibuat |
| `rejected` | Undangan ditolak oleh invitee |
| `expired` | Undangan kedaluwarsa sebelum direspon |
| `cancelled` | Undangan dibatalkan oleh pengirim |

### User.role

| Nilai | Deskripsi |
| - | - |
| `admin` | Administrator platform — akses ke manajemen seluruh company |
| `user` | Pengguna biasa — hanya mengakses company yang menjadi member |

### User.admin\_type

| Nilai | Deskripsi |
| - | - |
| `owner` | Owner platform — full access ke seluruh fitur aplikasi |
| `basic` | Admin basic — hanya akses transaksi produk digital |

> **Catatan Penting**: `admin_type` adalah hak akses di level **platform/aplikasi**, bukan di level company. Company owner ditentukan oleh field `Company.owner_id`.

### User.admin\_tier

| Nilai | Deskripsi |
| - | - |
| `none` | Tidak memiliki hak admin company (default) |
| `business` | Admin tier business — company management dasar |
| `advanced` | Admin tier advanced — company management lanjutan |
| `enterprise` | Admin tier enterprise — company management penuh |

### User.subscription\_plan

| Nilai | Deskripsi |
| - | - |
| `free` | Plan gratis — fitur dasar |
| `pro` | Plan profesional — fitur lanjutan |
| `business` | Plan bisnis — multi-company, RBAC penuh |
| `advanced` | Plan advanced — fitur enterprise parsial |
| `enterprise` | Plan enterprise — semua fitur tanpa batasan |

### User.membership\_duration\_type

| Nilai | Deskripsi |
| - | - |
| `monthly` | Membership bulanan |
| `yearly` | Membership tahunan |
| `custom` | Durasi kustom |
| `lifetime` | Membership seumur hidup |

### Company.industry

| Nilai | Deskripsi |
| - | - |
| `retail` | Ritel dan perdagangan |
| `manufacturing` | Manufaktur dan produksi |
| `services` | Jasa dan layanan |
| `technology` | Teknologi dan perangkat lunak |
| `food_beverage` | Makanan dan minuman (F\&B) |
| `healthcare` | Kesehatan dan farmasi |
| `education` | Pendidikan dan pelatihan |
| `other` | Industri lainnya |

### MenuAccessProfile.role

| Nilai | Deskripsi |
| - | - |
| `owner` | Profil akses untuk role owner |
| `admin` | Profil akses untuk role admin |
| `supervisor` | Profil akses untuk role supervisor |
| `store_admin` | Profil akses untuk role store admin |
| `stock_admin` | Profil akses untuk role stock admin |
| `finance_admin` | Profil akses untuk role finance admin |
| `hr_admin` | Profil akses untuk role HR admin |
| `transaction_admin` | Profil akses untuk role transaction admin |
| `employee` | Profil akses untuk role employee |

### MenuAccessProfile.dashboard\_type

| Nilai | Deskripsi |
| - | - |
| `owner` | Dashboard pemilik — ringkasan seluruh aspek bisnis |
| `admin` | Dashboard admin — ringkasan operasional |
| `supervisor` | Dashboard supervisor — monitoring read-only |
| `employee` | Dashboard karyawan — tugas dan aktivitas pribadi (default) |
| `cashier` | Dashboard kasir — antrian dan transaksi POS |
| `finance` | Dashboard keuangan — arus kas, invoice, expense |
| `hr` | Dashboard HR — absensi, payroll, leave |
| `inventory` | Dashboard inventaris — stok, pergerakan, alert |

***

## Role Assignment Lifecycle — State Diagram

```mermaid theme={null}
stateDiagram-v2
    [*] --> InvitationCreated : Owner/Admin mengirim undangan
    
    state InvitationCreated {
        [*] --> pending
        pending --> accepted : Invitee klik link & terima
        pending --> rejected : Invitee menolak undangan
        pending --> expired : Melewati expires_at
        pending --> cancelled : Owner/Admin membatalkan
    }
    
    accepted --> MemberCreated : Server membuat CompanyMember
    MemberCreated --> ActiveMember : Status = active, role & permissions aktif
    ActiveMember --> InactiveMember : Admin menonaktifkan member
    InactiveMember --> ActiveMember : Admin mengaktifkan kembali
    ActiveMember --> RoleChanged : Admin mengubah role/permissions
    RoleChanged --> ActiveMember : Permissions baru diterapkan
    ActiveMember --> MemberRemoved : Admin menghapus member
    MemberRemoved --> [*]
    
    rejected --> [*]
    expired --> [*]
    cancelled --> [*]
    
    note right of ActiveMember
        Permissions di-resolve via
        useCompanyRole hook
        dengan cache 60 detik
    end note
    
    note right of RoleChanged
        BroadcastChannel mengirim
        event 'snishop_member_updates'
        ke semua tab yang terbuka
    end note
```

***

## Sequence Diagrams — Alur Utama

### 1. Member Invitation Flow

```mermaid theme={null}
sequenceDiagram
    participant O as Owner/Admin
    participant RM as RoleManager
    participant SF as Server Function
    participant E as Email Service
    participant I as Invitee

    O->>RM: Klik "Invite Member"
    O->>RM: Masukkan email + pilih role
    O->>RM: Atur permissions dari matrix
    O->>RM: Submit undangan
    
    RM->>RM: Generate idempotency key
    RM->>SF: createCompanyInvitation(email, role, permissions, key)
    SF->>SF: Cek apakah user sudah terdaftar
    alt User sudah terdaftar
        SF->>SF: Buat CompanyMember langsung
    else User belum terdaftar
        SF->>SF: Buat CompanyInvitation (status: pending)
        SF->>E: Kirim email undangan
    end
    SF-->>RM: Return record yang dibuat
    RM->>RM: Track analytics: member_invited
    RM-->>O: Tampilkan pesan sukses
    
    E-->>I: Terima email undangan
    I->>I: Klik link accept
    I->>SF: Accept invitation
    SF->>SF: Buat CompanyMember (status: active)
    SF-->>I: Redirect ke dashboard
```

### 2. Permission Check Flow

```mermaid theme={null}
sequenceDiagram
    participant C as Komponen UI
    participant H as useCompanyRole Hook
    participant Cache as In-Memory Cache
    participant BC as BroadcastChannel
    participant SF as Server Function
    
    C->>H: Request permissions untuk company_id
    H->>Cache: Cek cache (TTL 60 detik)
    
    alt Cache valid (< 60 detik)
        Cache-->>H: Return cached permissions
    else Cache expired atau kosong
        H->>H: Cek apakah fetch sedang in-flight
        alt Fetch in-flight ada
            H->>H: Tunggu fetch yang sedang berjalan
        else Tidak ada fetch in-flight
            H->>H: Tandai fetch sebagai in-flight
            H->>SF: Fetch permissions dari server
            SF-->>H: Return permissions data
            H->>Cache: Simpan ke cache (reset TTL 60s)
            H->>H: Hapus dari in-flight map
        end
    end
    
    H-->>C: Return resolved permissions
    C->>C: Render UI berdasarkan permissions
    
    Note over BC: Real-time invalidation
    BC-->>H: Event 'snishop_member_updates'
    H->>Cache: Invalidate cache untuk company_id
    H->>C: Trigger re-fetch permissions
```

### 3. Menu Access Filtering Flow

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant SB as Sidebar Navigation
    participant MAP as MenuAccessProfile
    participant MS as MenuSettings
    participant H as useCompanyRole Hook
    
    U->>SB: Buka halaman aplikasi
    SB->>H: Request permissions & role user
    H-->>SB: Return { role, permissions }
    
    SB->>MAP: Query profil akses untuk role & company_id
    MAP-->>SB: Return { allowed_menus, dashboard_type }
    
    SB->>MS: Query semua menu (is_active = true)
    MS-->>SB: Return daftar menu
    
    SB->>SB: Filter menu:
    Note right of SB
        1. Menu harus ada di allowed_menus
        2. Menu harus aktif (is_active = true)
        3. Menu harus visible untuk subscription plan user
        4. Parent menu ditampilkan jika ada child yang visible
    end note
    
    SB-->>U: Tampilkan sidebar dengan menu yang di-filter
```

### 4. Role Assignment Flow

```mermaid theme={null}
sequenceDiagram
    participant O as Owner
    participant RM as RoleManager
    participant SF as Server Function
    participant BC as BroadcastChannel

    O->>RM: Pilih member dari daftar
    O->>RM: Ubah role dari dropdown
    RM->>RM: Update permission matrix otomatis
    O->>RM: Customize permissions individual
    O->>RM: Klik "Save Changes"
    
    RM->>RM: Generate idempotency key
    RM->>SF: updateCompanyMemberRole(member_id, role, permissions, key)
    SF->>SF: Validasi: hanya owner yang bisa assign role owner
    SF->>SF: Validasi SoD rules
    SF->>SF: Update CompanyMember di database
    SF-->>RM: Return updated record
    
    RM->>BC: Broadcast 'snishop_member_updates'
    RM->>RM: Track analytics: role_permission_updated
    RM-->>O: Tampilkan pesan sukses
    
    Note over BC: Semua tab menerima broadcast
    BC-->>RM: Tab lain invalidate cache
```

***

## Segregation of Duties (SoD)

3 aturan kritis untuk mencegah fraud dan conflict of interest:

| Rule ID | Rule | Permission yang Bertentangan | Rationale |
| - | - | - | - |
| **OPN-02** | Stock Opname counter tidak bisa jadi approver | `can_count_stock_opname` vs `can_approve_stock_opname` | Mencegah self-approval — orang yang hitung tidak boleh approve hasil hitung |
| **MFG-07** | Production operator tidak bisa jadi QC approver | `can_create_production_batch` vs `can_release_production_qc` | Mencegah self-verification — orang yang produksi tidak boleh approve kualitas |
| **CUS-01** | CS tidak bisa verifikasi transaksi tunai | `can_verify_b2b_payment` tidak boleh untuk CS Agent | Mencegah fraud — customer service tidak boleh handle cash verification |

**SoD Validation Matrix**:

```mermaid theme={null}
graph LR
    A[User with Role X] --> B{SoD Check}
    B -->|OPN-02| C["Tidak boleh memiliki<br/>can_count_stock_opname DAN<br/>can_approve_stock_opname"]
    B -->|MFG-07| D["Tidak boleh memiliki<br/>can_create_production_batch DAN<br/>can_release_production_qc"]
    B -->|CUS-01| E["CS Agent tidak boleh memiliki<br/>can_verify_b2b_payment"]
    
    C --> F[Permission Denied]
    D --> F
    E --> F
```

### Validasi SoD di Level Server

Validasi SoD dilakukan di dua lapisan:

1. **Client-side (RoleManager.jsx)** — Memberikan warning visual saat admin mengatur permissions yang bertentangan. Admin harus konfirmasi eksplisit untuk melanjutkan.
2. **Server-side (Server Function)** — Menolak request yang melanggar aturan SoD dengan error message yang jelas. Ini adalah lapisan pertahanan terakhir yang tidak bisa di-bypass dari client.

***

## Fitur Utama

### 1. Member Invitation Flow

Proses invitation menggunakan idempotency key untuk mencegah duplikasi:

```mermaid theme={null}
flowchart TD
    A[Owner/Admin klik Invite] --> B[Input email & pilih role]
    B --> C[Atur permissions dari matrix]
    C --> D[Generate idempotency key]
    D --> E{User sudah terdaftar?}
    E -->|Ya| F[Buat CompanyMember langsung]
    E -->|Tidak| G[Buat CompanyInvitation]
    G --> H[Kirim email undangan]
    F --> I[Track analytics]
    H --> I
    I --> J[Tampilkan sukses]
```

### 2. Permission Resolution

`useCompanyRole.jsx` hook menggunakan aggressive caching untuk performa:

| Feature | Value | Deskripsi |
| - | - | - |
| Cache TTL | 60 seconds | Permission data cached 60 detik |
| Auto-refresh | 10 seconds | Polling setiap 10 detik |
| Deduplication | In-flight Map | Mencegah duplicate fetches |
| Invalidation | BroadcastChannel | Instant cache clear on changes |

**Resolution Flow**:

```mermaid theme={null}
flowchart TD
    A[Komponen butuh permissions] --> B{Cache valid?<br/>< 60 detik}
    B -->|Ya| C[Return cached permissions]
    B -->|Tidak| D{Fetch sudah in-flight?}
    D -->|Ya| E[Tunggu fetch yang pending]
    D -->|Tidak| F[Fetch dari server]
    F --> G[Simpan di cache]
    G --> H[Return permissions]
    E --> H
```

### 3. Owner Role Protection

Hanya original company owner yang bisa assign role `owner`:

```mermaid theme={null}
flowchart TD
    A[User mencoba assign role owner] --> B{Apakah pengassign<br/>original owner?}
    B -->|Tidak| C[Permission Denied]
    B -->|Ya| D{Apakah target<br/>sudah owner?}
    D -->|Ya| E[Warning: Transferring ownership]
    D -->|Tidak| F[Izinkan assignment]
    E --> F
```

**Check**: `selectedCompany.owner_email === currentUser.email`

### 4. ERPAccessGuard

`ERPAccessGuard.jsx` (569 baris) melindungi company-specific pages:

**Features**:

| Feature | Deskripsi |
| - | - |
| Authentication check | Verifikasi user sudah login |
| Company context check | Verifikasi active company exists |
| Membership check | Verifikasi user adalah member company |
| Permission check | Verifikasi user memiliki permissions yang dibutuhkan |
| Grace period | 3-day read-only setelah membership expired |
| Real-time sync | Listen BroadcastChannel untuk permission updates |

**Grace Period**:

```mermaid theme={null}
flowchart TD
    A[User akses halaman company] --> B{Status membership?}
    B -->|Active| C[Akses penuh]
    B -->|Expired| D{Dalam grace period 3 hari?}
    D -->|Ya| E[Akses read-only]
    D -->|Tidak| F[Akses ditolak]
    B -->|Suspended| F
```

### 5. Multi-Role Support

Satu user bisa memiliki lebih dari satu role di company yang berbeda:

**Permission Merging**:

| Role A | Role B | Hasil Merge |
| - | - | - |
| View inventory | Edit inventory | Edit inventory (higher wins) |
| View finance | No access | View finance (union) |
| Create orders | Process payments | Kedua permissions |

**Example**:

```
User: Budi
Company A: Kasir (can_view_pos, can_use_pos)
Company B: Stock Admin (can_view_inventory, can_edit_inventory)
Hasil:
  - Di Company A: Hanya bisa akses POS
  - Di Company B: Hanya bisa akses inventaris
  - active_company_id menentukan company mana yang sedang aktif
```

### 6. Audit Trail

Setiap perubahan user/role dicatat di audit log:

| Action | Description |
| - | - |
| `create_company_invitation` | Undang member baru |
| `update_member_role` | Ubah role member |
| `delete_company_member` | Hapus member dari company |

**Audit Log Detail**:

| Field | Deskripsi |
| - | - |
| Actor | User yang melakukan aksi |
| Action | Jenis aksi yang dilakukan |
| Target | Member yang diubah |
| Old Value | Role/permissions sebelum perubahan |
| New Value | Role/permissions setelah perubahan |
| Timestamp | Waktu aksi dilakukan |
| IP Address | IP address dari actor |

***

## Cara Akses

Menu: **Admin** > **User Management** atau **Settings** > **Company Settings** > **Members & Roles**

## Flow Penggunaan

### Admin / Owner

1. Buka User Management dari sidebar
2. Klik "Invite Member" untuk invite user baru
3. Enter email dan pilih role dari 13 role templates
4. Customize permissions dari matrix 38 flag
5. Send invitation — sistem otomatis cek apakah user sudah terdaftar
6. Monitor invitation status (pending/accepted/rejected/expired)
7. Untuk existing members: edit role dan permissions sesuai kebutuhan
8. Review audit trail untuk security monitoring
9. Pastikan SoD rules tidak dilanggar saat assign permissions

### Member

1. Terima invitation email
2. Click accept link
3. Login atau register jika belum punya akun
4. Auto-joined ke company dengan role yang di-assign
5. Akses fitur sesuai permissions yang diberikan
6. Sidebar navigasi otomatis ter-filter sesuai MenuAccessProfile

***

## Integrasi Cross-Module

```mermaid theme={null}
graph LR
    A[RBAC System] --> B[CompanyAccessControl<br/>Central hub]
    A --> C[RoleManager<br/>Member management]
    A --> D[useCompanyRole<br/>Permission hook]
    A --> E[ERPAccessGuard<br/>Route protection]
    A --> F[AuditTrailViewer<br/>Security logs]
    
    B --> G[3 SoD Rules]
    C --> H[38 Permissions]
    D --> I[60s Cache]
    E --> J[3-Day Grace]
    F --> K[Append-only logs]
    
    A --> L[Sidebar Navigation<br/>Menu filtering]
    A --> M[Dashboard<br/>Role-based variant]
    A --> N[All ERP Pages<br/>Permission check]
    A --> O[MenuAccessProfile<br/>Profil akses menu]
    A --> P[MenuSettings<br/>Konfigurasi menu]
```

### Hubungan dengan Modul Lain

| Modul | Integrasi RBAC | Deskripsi |
| - | - | - |
| **POS/Kasir** | `can_view_pos`, `can_use_pos`, `can_manage_cashier_shift` | Kontrol akses ke fitur POS dan shift management |
| **Inventaris** | `can_view_inventory`, `can_edit_inventory`, `can_transfer_inventory` | Kontrol akses ke stok, pergerakan, dan transfer |
| **Manufacturing** | `can_create_production_batch`, `can_release_production_qc` | Kontrol akses ke produksi dan QC dengan SoD |
| **Finance** | `can_view_finance`, `can_edit_finance` | Kontrol akses ke invoice, expense, dan laporan keuangan |
| **HR** | `can_view_hr`, `can_edit_hr` | Kontrol akses ke data karyawan, absensi, dan payroll |
| **Stock Opname** | `can_count_stock_opname`, `can_approve_stock_opname` | Kontrol akses dengan SoD: counter tidak bisa jadi approver |
| **Distribution** | `can_view_distribution`, `can_create_distribution_shipment`, `can_confirm_distribution_shipment` | Kontrol akses ke distribusi dan shipment |
| **B2B** | `can_view_b2b_invoices`, `can_create_b2b_invoice`, `can_verify_b2b_payment` | Kontrol akses ke invoice dan pembayaran B2B |
| **Settings** | `can_view_settings`, `can_edit_settings` | Kontrol akses ke pengaturan company |
| **Dashboard** | `dashboard_type` dari MenuAccessProfile | Menentukan variant dashboard yang ditampilkan |

***

## Row-Level Security (RLS)

Entitas `CompanyMember` dan `CompanyInvitation` dilindungi oleh Row-Level Security policies yang memastikan setiap user hanya bisa mengakses data yang relevan:

### Kondisi Akses CompanyMember

Sebuah record `CompanyMember` dapat dibaca/ditulis jika **salah satu** kondisi berikut terpenuhi:

1. **Active company match**: `data.company_id === user.active_company_id` dan `company_id` tidak null/kosong
2. **Creator**: `created_by_id === user.id` — user yang membuat record
3. **Self-access**: `data.user_email === user.email` atau `data.user_id === user.id` — data milik sendiri
4. **Platform admin**: `user.role === 'admin'` — administrator platform

### Kondisi Akses CompanyInvitation

Sebuah record `CompanyInvitation` dapat dibaca/ditulis jika **salah satu** kondisi berikut terpenuhi:

1. **Active company match**: `data.company_id === user.active_company_id` dan `company_id` tidak null/kosong
2. **Creator**: `created_by_id === user.id` — user yang mengirim undangan
3. **Invitee**: `data.invited_email === user.email` — user yang diundang
4. **Platform admin**: `user.role === 'admin'` — administrator platform

***

## Security Best Practices

### 1. Principle of Least Privilege

Beri akses minimal yang dibutuhkan untuk pekerjaan. Jangan beri Admin role kalau cuma butuh Kasir.

### 2. Regular Review

Review user roles setiap bulan:

* Hapus user yang sudah resign
* Adjust role jika ada perubahan tanggung jawab
* Cek audit log untuk aktivitas mencurigakan
* Pastikan `assigned_locations` masih relevan

### 3. SoD Enforcement

Selalu enforce Segregation of Duties:

* Stock opname: counter ≠ approver
* Manufacturing: operator ≠ QC
* Finance: CS ≠ cash verifier

### 4. Owner Protection

Hanya transfer ownership setelah pertimbangan matang — original owner punya kontrol penuh atas company.

### 5. Location-Based Access

Gunakan `assigned_locations` untuk membatasi akses gudang/outlet per member. Ini penting untuk perusahaan multi-cabang yang ingin memastikan setiap staff hanya mengakses lokasi yang menjadi tanggung jawabnya.

### 6. Invitation Hygiene

* Set `expires_at` yang wajar (rekomendasi: 7 hari)
* Cancel undangan yang tidak direspon dalam waktu lama
* Jangan share invitation link di channel yang tidak aman
* Verifikasi email invitee sebelum mengirim undangan

***

## Tips

* 13 role templates mencakup sebagian besar use case — buat custom role hanya jika benar-benar butuh
* SoD rules adalah anti-fraud control — jangan bypass kecuali ada alasan kuat dan compensating control
* BroadcastChannel sync memastikan permission changes propagate instantly ke semua tabs
* Grace period 3 hari memberi waktu untuk renew membership sebelum user terblokir total
* Audit trail bersifat append-only — tidak bisa dihapus, memastikan forensic integrity
* Permission caching (60s TTL) balancing antara performa dan freshness — adjust jika perlu
* Gunakan `MenuAccessProfile` untuk mengontrol visibility sidebar per role — lebih fleksibel daripada hardcode
* Field `assigned_locations` pada CompanyMember mendukung RBAC berbasis lokasi — ideal untuk bisnis multi-cabang
* `working_hours` pada CompanyMember dapat digunakan untuk time-based access control di masa depan
* `dashboard_type` pada MenuAccessProfile memungkinkan setiap role melihat dashboard yang berbeda dan relevan dengan fungsinya


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