# General Affairs Module — Design Spec
**Date:** 2026-04-10
**Status:** Approved

---

## 1. Overview

Modul baru **General Affairs** ditambahkan ke sidebar navigasi utama. Mencakup manajemen operator, penjadwalan shift, dan absensi (bukti hadir). Purchase Request akan dibahas di spec terpisah.

**Sub-menu:**
| Sub-menu | URL | Keterangan |
|---|---|---|
| Operators | `/general-affairs/operators` | Daftar operator per SPBU |
| Jadwal | `/general-affairs/jadwal` | Grid jadwal shift mingguan/bulanan |
| Absensi | `/general-affairs/absensi` | Bukti hadir foto + approval |

---

## 2. Database Schema

### 2.1 Perubahan tabel existing

**`master_role`** — tambah 1 kolom:
```sql
can_be_scheduled BOOLEAN DEFAULT FALSE NOT NULL
```
Migration: `ALTER TABLE master_role ADD COLUMN can_be_scheduled BOOLEAN DEFAULT FALSE NOT NULL`

Seed update default values:
| Role | can_be_scheduled |
|---|---|
| Operator | TRUE |
| SPBU Admin | FALSE |
| Manager | FALSE |
| Viewer | FALSE |

### 2.2 Tabel baru: `jadwal_shift`

```sql
CREATE TABLE jadwal_shift (
    id              SERIAL PRIMARY KEY,
    spbu_id         INTEGER NOT NULL REFERENCES master_spbu(id) ON DELETE CASCADE,
    user_id         INTEGER NOT NULL REFERENCES master_user(id) ON DELETE CASCADE,
    shift_id        INTEGER NOT NULL REFERENCES master_spbu_shift(id) ON DELETE CASCADE,
    tanggal         DATE NOT NULL,
    created_by_id   INTEGER REFERENCES master_user(id) ON DELETE SET NULL,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at      TIMESTAMP WITH TIME ZONE,
    UNIQUE (spbu_id, user_id, shift_id, tanggal)
);
```

- Soft delete via `deleted_at`
- Hanya user dengan `can_be_scheduled=true` role yang bisa di-assign

### 2.3 Tabel baru: `absensi`

```sql
CREATE TABLE absensi (
    id              SERIAL PRIMARY KEY,
    spbu_id         INTEGER NOT NULL REFERENCES master_spbu(id) ON DELETE CASCADE,
    shift_id        INTEGER NOT NULL REFERENCES master_spbu_shift(id) ON DELETE CASCADE,
    tanggal         DATE NOT NULL,
    foto_url        VARCHAR(500),           -- Google Drive URL
    uploaded_by_id  INTEGER REFERENCES master_user(id) ON DELETE SET NULL,
    uploaded_at     TIMESTAMP WITH TIME ZONE,
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',  -- pending | approved
    reviewed_by_id  INTEGER REFERENCES master_user(id) ON DELETE SET NULL,
    reviewed_at     TIMESTAMP WITH TIME ZONE,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE (spbu_id, shift_id, tanggal)
);
```

- 1 foto group per shift per hari per SPBU
- Upload → status `pending`, setelah approve → `approved`
- Foto dikompresi sebelum upload ke GDrive (max 1280px, JPEG quality 75%)

---

## 3. Backend API

**Base prefix:** `/api/v1/`

### 3.1 Operators
```
GET /spbus/{spbu_id}/operators
```
- Auth: `operators:view`
- Response: list `{ id, nama, posisi (role nama), is_active }`
- Filter: hanya user dengan role `can_be_scheduled=true` di SPBU tersebut

### 3.2 Jadwal
```
GET    /spbus/{spbu_id}/jadwal?start=YYYY-MM-DD&end=YYYY-MM-DD
POST   /spbus/{spbu_id}/jadwal          -- body: array of { user_id, shift_id, tanggal }
DELETE /spbus/{spbu_id}/jadwal/{id}     -- soft delete
```
- Auth: `jadwal:view` / `jadwal:create` / `jadwal:delete`
- POST mendukung bulk insert (array) untuk efisiensi grid save
- Validasi: user harus assigned ke SPBU tersebut dan rolenya `can_be_scheduled=true`

### 3.3 Absensi
```
GET   /spbus/{spbu_id}/absensi?start=YYYY-MM-DD&end=YYYY-MM-DD
POST  /spbus/{spbu_id}/absensi          -- multipart/form-data: shift_id, tanggal, foto
PATCH /spbus/{spbu_id}/absensi/{id}/approve
```
- Auth: `absensi:view` / `absensi:create` / `absensi:approve`
- POST: compress foto → upload GDrive → simpan URL + status `pending`
- PATCH approve: set `status=approved`, `reviewed_by_id`, `reviewed_at`

### 3.4 Roles (existing endpoint, extend schema)
```
PATCH /roles/{role_id}
```
- Tambah field `can_be_scheduled: bool` di `RoleUpdate` schema

### 3.5 Pattern
Router → Service → Repository → Model. Tidak ada raw query di router.

---

## 4. Image Compression

Utility baru: `backend/app/utils/image.py`

```python
def compress_image(file_bytes: bytes, max_px: int = 1280, quality: int = 75) -> bytes:
    """
    Resize (maintain aspect ratio) dan compress ke JPEG.
    Hasil estimasi 100–300KB vs original 3–8MB.
    """
```

Dipanggil di absensi service sebelum `save_upload()`:
```
raw_bytes → compress_image() → gdrive_upload() → simpan URL
```

Dependencies: `Pillow` (tambah ke requirements)

---

## 5. Frontend

### 5.1 Sidebar
Tambah menu **General Affairs** (`ti-briefcase` icon) dengan sub-items:
- Operators
- Jadwal
- Absensi

### 5.2 `/general-affairs/operators`
- Tabel: Nama, Posisi (role nama), Status badge (Active/Inactive)
- Filter SPBU (untuk Super Admin)
- Read-only — manajemen user tetap di `/users`

### 5.3 `/general-affairs/jadwal`
**Grid Excel-style:**
- Rows = operator (nama)
- Columns = tanggal
- Cell = nama shift yang dijadwalkan (kosong = tidak dijadwal)
- Toolbar: pilih SPBU, pilih periode (weekly/monthly), toggle view
- Interaksi: klik cell kosong → dropdown pilih shift → save; klik cell terisi → hapus
- Bulk save: perubahan di-batch, 1x POST array ke backend

### 5.4 `/general-affairs/absensi`
**Tampilan kondisional berdasarkan permission:**

| Permission | View |
|---|---|
| `absensi:create` | Tabel per shift per hari + tombol "Upload Foto" |
| `absensi:approve` | Tabel semua absensi + thumbnail foto + tombol "Approve" |

- Upload: modal → pilih file → preview thumbnail → Submit
- 1 foto per shift per hari (group photo)
- 1 tombol Approve per baris
- Foto sudah dikompresi di backend sebelum disimpan ke GDrive

---

## 6. Permissions & Roles

### 6.1 Modul baru di permission matrix

| Modul | view | create | edit | delete | approve |
|---|---|---|---|---|---|
| `operators` | ✅ | — | — | — | — |
| `jadwal` | ✅ | ✅ | ✅ | ✅ | — |
| `absensi` | ✅ | ✅ | — | — | ✅ |

### 6.2 Default permissions per role

| Role | operators | jadwal | absensi |
|---|---|---|---|
| SPBU Admin | view | view, create, edit, delete | view, create, approve |
| Manager | view | view | view, approve |
| Operator | view | view | view, create |
| Viewer | view | view | view |

### 6.3 Roles & Permissions UI update
- Tambah toggle `Can be scheduled` di setiap role card
- Tambah 3 modul baru (`operators`, `jadwal`, `absensi`) di permission matrix checklist

---

## 7. Modules Not In Scope

- **Purchase Request** — dibahas di spec terpisah
- **Clock-in/clock-out** — tidak ada, hanya foto bukti hadir
- **Payroll / kalkulasi gaji** — tidak ada
- **Notifikasi** — tidak ada di fase ini

---

## 8. Dependencies Baru

| Layer | Package |
|---|---|
| Backend | `Pillow` (image compression) |
| Frontend | tidak ada package baru |

---

*Spec disetujui: 2026-04-10*
