# Master divisi & CRUD — BE

**Canonical di repo FE:** [`../../FE/docs/BE_SPEC_ORG_DIVISIONS_MASTER_AND_CRUD.md`](../../FE/docs/BE_SPEC_ORG_DIVISIONS_MASTER_AND_CRUD.md) — salinan di repo BE ini; sesuaikan tautan relatif jika struktur checkout berbeda.

**Handoff Employees / simpan divisi & anggota:** [`BE_HANDOFF_ORG_DIVISIONS_AND_MEMBERS.md`](./BE_HANDOFF_ORG_DIVISIONS_AND_MEMBERS.md)

**404 `GET /org/divisions` (deploy / proxy):** [`BE_DEPLOY_ORG_DIVISIONS_API_404_RESOLUSI.md`](./BE_DEPLOY_ORG_DIVISIONS_API_404_RESOLUSI.md)

**Perilaku Team Monitoring (divisi):** [`BE_SPEC_TEAM_MONITORING_DIVISIONS.md`](./BE_SPEC_TEAM_MONITORING_DIVISIONS.md)

---

## Status opsi B di BE

| Opsi | Arti | Status di BE |
|------|------|----------------|
| **A** | Divisi = string **`reports.division`**; monitoring menampilkan nilai unik per periode. | Tetap berlaku bila **tidak ada** baris **`org_divisions` aktif**. |
| **B** | Master **`org_divisions`** + **`org_division_members`** (`org_role`) + validasi laporan + merge analytics. | **Diimplementasikan** — migrasi `000003`–`000005`, handler `internal/api/org_divisions.go`, rute di `internal/api/router.go`. |

---

## Model data

- **`org_divisions`:** `id`, `code` (unik case-insensitive), `name`, **`description`** (opsional), `sort_order`, `is_active`, `created_at`, `updated_at`.
- **`org_division_members`:** `id`, `division_id`, `user_id`, **`org_role`** (string, default `staff`, max 64 karakter), `created_at`, `updated_at`, **UNIQUE (`division_id`, `user_id`)** — satu baris per pasangan; user boleh menjadi anggota **beberapa** divisi.
- Migrasi **`000005`** memindahkan data dari **`users.primary_org_division_id`** (000004) ke `org_division_members` lalu menghapus kolom tersebut.

---

## Kontrak API (implementasi)

Prefix **`/api/v1/org/divisions`**, envelope `httpapi.OK` / `httpapi.Fail`.

| Metode | Path | Auth | Fungsi |
|--------|------|------|--------|
| `GET` | `/org/divisions` | Pengguna login | Daftar. Query: `limit`, `offset`, `active` (`1` \| `0` \| `all`; `0`/`all` hanya **superadmin**). |
| `GET` | `/org/divisions/:id/members` | Superadmin | Anggota dari **`org_division_members`** + join `users`. |
| `PUT` | `/org/divisions/:id/members` | Superadmin | **Sinkron penuh** anggota divisi ini: hapus semua baris anggota untuk `division_id`, lalu insert ulang. Body: **`members`** (disarankan) `[{ "userId", "orgRole?" }]` — `orgRole` kosong = **`staff`**; dan/atau legacy **`userIds`** (semua peran `staff`). Divisi harus **aktif**. |
| `POST` | `/org/divisions/:id/members` | Superadmin | Upsert satu: `{ "userId", "orgRole?" }`. `ON CONFLICT` memperbarui `org_role`. Divisi harus **aktif**. |
| `DELETE` | `/org/divisions/:id/members/:userId` | Superadmin | Hapus baris anggota untuk pasangan tersebut. |
| `POST` | `/org/divisions` | Superadmin | Buat: `code`, `name`, `description?`, `sortOrder?`. |
| `PATCH` | `/org/divisions/:id` | Superadmin | Ubah `name`, **`description`** (string kosong = NULL), `sortOrder`, `isActive`. |
| `DELETE` | `/org/divisions/:id` | Superadmin | **Soft delete:** `is_active = false`. |

Respons `GET` daftar: tiap divisi — `id`, `code`, `name`, `description?`, `sortOrder`, `isActive`, `createdAt`, `updatedAt`.

Respons `GET .../:id/members`: `{ "divisionId", "members": [ { "userId", "name", "email", "isActive", "orgRole" } ] }`.

Respons `POST .../members`: `{ "divisionId", "userId", "orgRole" }`.

---

## Laporan (`reports.division`)

- Jika **setidaknya satu** baris **`org_divisions` dengan `is_active = true`** ada: `POST` / `PATCH` **`/reports`** — field `division` harus kosong (NULL) atau **`code` aktif** (case-insensitive); nilai tersimpan = **`code` kanonik** dari master.
- Jika **tidak ada** master aktif: string bebas (opsi A).

---

## Analytics org

- **`GET /analytics/org/by-division`** (superadmin): agregat dari laporan; merge dengan master aktif bila ada — lihat kode `analytics_org.go`.
- **`GET /analytics/org/reporters-by-division`**: filter dari **`reports.division`**, bukan dari `org_division_members`.

---

## File yang disentuh

- `internal/db/migrations/000003_org_divisions.*.sql`
- `internal/db/migrations/000004_users_primary_org_division.*.sql`
- `internal/db/migrations/000005_org_division_members.*.sql`
- `internal/api/org_divisions.go`, `internal/api/router.go`
- `internal/api/rest.go`, `internal/api/analytics_org.go`

Direktori Employees: [`BE_SPEC_EMPLOYEE_ORG_MANAGEMENT.md`](./BE_SPEC_EMPLOYEE_ORG_MANAGEMENT.md).

---

## Realtime

Tidak wajib untuk CRUD; lihat [`BE_SPEC_TEAM_MONITORING_DIVISIONS.md`](./BE_SPEC_TEAM_MONITORING_DIVISIONS.md).
