# Dokumentasi Aplikasi FinNest (Koperasi)

Dokumentasi keseluruhan aplikasi manajemen koperasi berbasis Laravel.

---

## Daftar isi

1. [Gambaran umum](#1-gambaran-umum)
2. [Tech stack](#2-tech-stack)
3. [Arsitektur & struktur folder](#3-arsitektur--struktur-folder)
4. [Autentikasi & otorisasi](#4-autentikasi--otorisasi)
5. [Modul bisnis](#5-modul-bisnis)
6. [Skema database](#6-skema-database)
7. [Alur keuangan](#7-alur-keuangan)
8. [Upload bukti file](#8-upload-bukti-file)
9. [Laporan PDF](#9-laporan-pdf)
10. [Portal anggota](#10-portal-anggota)
11. [Rute aplikasi](#11-rute-aplikasi)
12. [Seeder & data demo](#12-seeder--data-demo)
13. [Console command](#13-console-command)
14. [Aturan bisnis penting](#14-aturan-bisnis-penting)
15. [Virtual host Laragon & Cloudflare Tunnel](#15-virtual-host-laragon--cloudflare-tunnel)

---

## 1. Gambaran umum

**FinNest** adalah sistem informasi koperasi yang mendukung:

- Pencatatan anggota dan posisi simpanan/pinjaman
- Penerimaan kas via resi transfer & transaksi individual
- Akad jual beli barang secara cicilan
- Pendapatan unit usaha dan pengeluaran operasional
- Dashboard ringkasan untuk pengurus
- Portal self-service untuk anggota (hanya melihat data sendiri)
- Export laporan ke PDF

Aplikasi memisahkan dua dunia akses:

| Area | URL | Pengguna |
|------|-----|----------|
| Back-office | `/login`, `/dashboard`, modul CRUD | Admin & Management |
| Portal anggota | `/portal/login`, `/portal/*` | Anggota koperasi |

Akses host:

| Mode | Base URL |
|------|----------|
| Lokal (Laragon) | `http://koperasi-app.test` |
| Publik (Cloudflare Tunnel) | `https://koperasi.finnest.my.id` |

Login terpadu: satu halaman `/login` dengan tab **Anggota** dan **Pengurus**.  
`/portal/login` dialihkan ke `/login?tab=anggota`.

---

## 2. Tech stack

| Lapisan | Teknologi |
|---------|-----------|
| Backend | PHP ^8.2, Laravel ^12 |
| Auth scaffolding | Laravel Breeze |
| Frontend | Blade, Tailwind CSS, Alpine.js, Axios, Vite |
| PDF | `barryvdh/laravel-dompdf` |
| DB (default) | SQLite (`DB_CONNECTION=sqlite`) |
| Session / cache / queue | Database-driven (lihat `.env.example`) |
| Testing | PHPUnit |
| Code style | Laravel Pint |

Dependensi penting di `composer.json`:

- `laravel/framework`
- `barryvdh/laravel-dompdf`
- `laravel/breeze` (dev)

---

## 3. Arsitektur & struktur folder

```
koperasi-app/
├── app/
│   ├── Console/Commands/CleanupOrphanedFiles.php
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── MemberController.php
│   │   │   ├── ResiController.php
│   │   │   ├── TransaksiController.php
│   │   │   ├── AkadJualBeliController.php
│   │   │   ├── PendapatanUsahaController.php
│   │   │   ├── PengeluaranKoperasiController.php
│   │   │   ├── LaporanController.php
│   │   │   ├── MemberPortalController.php
│   │   │   └── ProfileController.php / Auth/*
│   │   └── Middleware/
│   │       ├── EnsureRole.php
│   │       └── EnsureReadOnly.php
│   ├── Models/
│   │   ├── User.php
│   │   ├── Member.php
│   │   ├── Resi.php
│   │   ├── Transaksi.php
│   │   ├── TransaksiDetail.php
│   │   ├── AkadJualBeli.php
│   │   ├── PendapatanUsaha.php
│   │   └── PengeluaranKoperasi.php
│   └── View/Components/   # AppLayout, GuestLayout, PortalLayout
├── bootstrap/app.php      # Alias middleware role & readonly
├── config/auth.php        # Guard web + member
├── database/migrations/
├── database/seeders/
├── resources/views/
│   ├── dashboard.blade.php
│   ├── members/, resi/, transaksi/, akad_jual_beli/
│   ├── pendapatan_usaha/, pengeluaran/, laporan/
│   ├── portal/
│   └── layouts/
└── routes/web.php         # Rute bisnis utama
```

Middleware terdaftar di `bootstrap/app.php`:

- `role` → `EnsureRole`
- `readonly` → `EnsureReadOnly`

---

## 4. Autentikasi & otorisasi

### 4.1 Guard

| Guard | Provider | Model | Dipakai untuk |
|-------|----------|-------|----------------|
| `web` | `users` | `User` | Admin & Management |
| `member` | `members` | `Member` | Portal anggota |

Jika autentikasi guard `member` gagal, exception diarahkan ke `portal.login`.

### 4.2 Role back-office (`users.role`)

| Role | Capability |
|------|------------|
| `admin` | Full CRUD |
| `management` | Hanya `GET`/`HEAD` pada grup modul (`EnsureReadOnly`) |

`EnsureRole` memastikan user login dan role cocok; jika tidak → 403.

### 4.3 Login portal anggota

- Identifier: **nomor telepon** (`no_telepon`)
- Password: di-hash (bcrypt), diatur oleh admin lewat form edit anggota
- Password di DB boleh null; tanpa password anggota tidak bisa masuk portal

---

## 5. Modul bisnis

### 5.1 Dashboard (`/dashboard`)

Hanya `admin` & `management` (middleware `auth`, `verified`, `role:admin,management`).

Menampilkan:

- Jumlah anggota (total / aktif / non-aktif)
- Total simpanan awal (`simpanan_pokok + simpanan_wajib` di tabel member)
- **Kas masuk** = total header transaksi `setoran` + total `pendapatan_usahas`
- **Kas keluar** = total header transaksi `penarikan` + total `pengeluaran_koperasis`
- **Saldo koperasi** = kas masuk − kas keluar
- 5 anggota terbaru
- Daftar pinjaman uang + cicilan + sisa + progress
- Ranking simpanan wajib & pokok (termasuk anggota dengan total 0)

### 5.2 Anggota (`/members`)

**Controller:** `MemberController`  
**Model:** `Member`

| Field utama | Keterangan |
|-------------|------------|
| `nomor_anggota` | Unik, saran format `KOP-####` |
| `nama`, `email`, `no_telepon`, `alamat` | Identitas |
| `tanggal_bergabung`, `status` | `aktif` / `non-aktif` |
| `simpanan_pokok`, `simpanan_wajib` | Saldo awal (sebelum sistem) |
| `iuran_wajib_bulanan` | Target iuran per bulan |
| `target_simpanan_pokok` | Target simpanan pokok |
| `password` | Akses portal |

**Halaman show** menghitung ulang dari transaksi:

- Total simpanan (pokok/wajib/sukarela) dari detail jenis terkait pada transaksi `setoran`
- Total pinjaman & cicilan pinjaman uang
- Progress simpanan wajib & pokok
- Histori transaksi

### 5.3 Resi (`/resi`)

**Controller:** `ResiController`  
**Model:** `Resi`

Resi = bukti transfer yang dapat menaungi **banyak transaksi anggota** sekaligus.

| Field | Keterangan |
|-------|------------|
| `no_resi` | Nomor resi |
| `tanggal` | Tanggal transfer |
| `jumlah_total` | Total nominal |
| `bukti_transfer` | Path file di `storage/app/public/bukti` |
| `keterangan` | Catatan |

Alur create:

1. Upload bukti (opsional)
2. Input array anggota + detail transaksi masing-masing
3. Sistem membuat 1 `transaksi` per anggota + `transaksi_details`
4. `jumlah_total` transaksi dihitung dari jumlah detail (bukan percaya input mentah)
5. Token UUID di session mencegah double submit

Menghapus resi → model event menghapus transaksi & detail terkait.

### 5.4 Transaksi (`/transaksi`)

**Controller:** `TransaksiController`  
**Model:** `Transaksi`, `TransaksiDetail`

#### Header transaksi

| Field | Keterangan |
|-------|------------|
| `no_transaksi` | Auto: `TRX-YYYYMM-###` |
| `member_id` | Anggota |
| `resi_id` | Opsional |
| `jenis` | `setoran` / `penarikan` |
| `tanggal_transaksi` | Tanggal |
| `jumlah_total` | Jumlah semua detail |
| `keterangan` | Catatan |

#### Jenis detail — setoran

| Jenis | Arti |
|-------|------|
| `simpanan_pokok` | Setoran simpanan pokok |
| `simpanan_wajib` | Setoran simpanan wajib |
| `simpanan_sukarela` | Setoran sukarela |
| `cicilan_pinjaman_uang` | Cicilan atas pinjaman uang |
| `cicilan_pembelian_barang` | Cicilan akad barang (bisa link `akad_jual_beli_id`) |
| `keuntungan` | Komponen keuntungan |

#### Jenis detail — penarikan

| Jenis | Arti |
|-------|------|
| `pinjaman_anggota` | Pencairan pinjaman uang |
| `pembelian_barang_jasa` | Pembelian melalui koperasi |

Endpoint JSON untuk form: `GET /members/{member}/akads-json` — daftar akad anggota (prioritas status `berjalan`).

### 5.5 Akad jual beli (`/akad-jual-beli`)

**Controller:** `AkadJualBeliController`  
**Model:** `AkadJualBeli`

Mencatat barang yang dibeli koperasi lalu dijual cicilan ke anggota.

| Field | Keterangan |
|-------|------------|
| `no_akad` | Auto: `AKD-YYYYMM-###` |
| `member_id` | Pembeli (anggota) |
| `tanggal_akad`, `nama_barang` | Info akad |
| `harga_pokok`, `keuntungan`, `harga_jual` | Nilai ekonomi |
| `tenor_bulan` | Lama cicilan |
| `status` | `berjalan` / `lunas` |
| `bukti_foto`, `bukti_files` | Lampiran |

Saat cicilan barang tertaut ke akad dan total cicilan ≥ `harga_jual`, status otomatis menjadi **`lunas`**.

### 5.6 Pendapatan usaha (`/pendapatan-usaha`)

Kas masuk unit usaha (bukan dari setoran anggota).

Jenis: `hasil_panen`, `sewa_lahan`, `keuntungan_dagang`, `lainnya`.

Filter index: pencarian, jenis, tahun + total nominal terfilter.

### 5.7 Pengeluaran koperasi (`/pengeluaran`)

Kas keluar operasional/modal.

Jenis: `pembelian_barang`, `modal_usaha`, `operasional`, `lainnya`.

### 5.8 Laporan (`/laporan`)

Lihat [bagian 9](#9-laporan-pdf).

### 5.9 Profil pengguna back-office

Rute Breeze: edit profil, ganti password, hapus akun (`admin` & `management`).

---

## 6. Skema database

### Diagram relasi (sederhana)

```mermaid
erDiagram
    users ||--o{ users : "role admin/management"
    members ||--o{ transaksis : has
    members ||--o{ akad_jual_belis : has
    resis ||--o{ transaksis : "optional"
    transaksis ||--|{ transaksi_details : has
    akad_jual_belis ||--o{ transaksi_details : "optional cicilan"
    pendapatan_usahas
    pengeluaran_koperasis
```

### Tabel

| Tabel | Fungsi |
|-------|--------|
| `users` | Pengurus (admin/management) |
| `members` | Anggota + kredensial portal |
| `resis` | Bukti transfer |
| `transaksis` | Header transaksi anggota |
| `transaksi_details` | Rincian jenis & nominal |
| `akad_jual_belis` | Akad cicilan barang |
| `pendapatan_usahas` | Pendapatan unit usaha |
| `pengeluaran_koperasis` | Pengeluaran koperasi |
| `cache`, `jobs`, `sessions`, … | Infrastruktur Laravel |

### Cascade penting

| Aksi | Efek |
|------|------|
| Hapus `member` | Cascade hapus transaksi & akad terkait |
| Hapus `transaksi` | Cascade hapus detail |
| Hapus `resi` | Model menghapus transaksi+detail; FK migrasi juga set null |
| Hapus `akad` | `transaksi_details.akad_jual_beli_id` → NULL |

---

## 7. Alur keuangan

### Kas masuk

```
Setoran anggota (transaksis.jenis = setoran)
+ Pendapatan usaha (pendapatan_usahas.jumlah)
────────────────────────────────────────────
= Kas masuk
```

### Kas keluar

```
Penarikan anggota (transaksis.jenis = penarikan)
+ Pengeluaran koperasi (pengeluaran_koperasis.jumlah)
────────────────────────────────────────────────────
= Kas keluar
```

### Saldo

```
Saldo koperasi = Kas masuk − Kas keluar
```

### Simpanan anggota

- Field di `members` = **saldo awal** (legacy / sebelum aplikasi)
- Mutasi setelah sistem hidup = agregasi `transaksi_details` pada transaksi `setoran`

### Pinjaman uang

```
Total pinjaman   = SUM(detail.jenis = pinjaman_anggota)
Total cicilan    = SUM(detail.jenis = cicilan_pinjaman_uang)
Sisa hutang      = max(0, pinjaman − cicilan)
Progress (%)     = cicilan / pinjaman × 100
```

### Cicilan barang (akad)

```
Terbayar  = SUM(detail cicilan_pembelian_barang yang linked ke akad)
Sisa      = harga_jual − terbayar
Progress  = terbayar / harga_jual × 100
```

---

## 8. Upload bukti file

Disk: `public` (`storage/app/public`, di-link ke `public/storage`).

| Modul | Folder | Field |
|-------|--------|-------|
| Resi | `bukti/` | `bukti_transfer` (single) |
| Akad | `akad-bukti/` | `bukti_foto` + `bukti_files` (JSON array) |
| Pendapatan | `pendapatan/` | `bukti` + `bukti_files` |
| Pengeluaran | `pengeluaran/` | `bukti` + `bukti_files` |

Validasi umum:

- Tipe: image
- Maksimal: **5 MB** per file

Perilaku:

- Ganti bukti utama → file lama dihapus
- `bukti_files` bersifat append saat edit
- Gagal transaksi create resi → file upload baru dibersihkan (anti orphan)

---

## 9. Laporan PDF

**Controller:** `LaporanController`  
**Library:** DomPDF

| Rute | Isi |
|------|-----|
| `GET /laporan` | Menu laporan |
| `GET /laporan/ringkasan` | Ringkasan keuangan (filter tanggal opsional) |
| `GET /laporan/transaksi` | Daftar transaksi per rentang tanggal |
| `GET /laporan/pinjaman-uang` | PDF landscape: pinjaman per anggota |
| `GET /laporan/cicilan-barang` | PDF landscape: progress per akad |

Catatan atribusi cicilan barang tanpa `akad_jual_beli_id`:

- Hanya diatribusikan jika anggota punya **tepat satu** akad (hindari ambiguitas).

Tidak ada export Excel/CSV saat ini.

---

## 10. Portal anggota

Prefix: `/portal`  
Guard: `auth:member`  
Layout: `layouts/portal.blade.php`

| Rute | Fungsi |
|------|--------|
| `GET /portal/login` | Form login (no. telepon + password) |
| `POST /portal/login` | Proses login |
| `POST /portal/logout` | Logout |
| `GET /portal` | Beranda: ringkasan simpanan, pinjaman, progres, akad |
| `GET /portal/histori-transaksi` | Histori transaksi |
| `GET /portal/cicilan-pinjaman` | Detail cicilan pinjaman uang |
| `GET /portal/cicilan-barang` | Detail cicilan per akad |
| `GET /portal/profil` | Profil anggota |
| `GET /portal/resi/{resi}` | Detail resi (hanya jika ada transaksi milik anggota) |

Portal bersifat **read-only**. Password diganti oleh admin di back-office.

---

## 11. Rute aplikasi

### Publik / guest

| Method | Path | Nama |
|--------|------|------|
| GET | `/` | redirect → `dashboard` |
| GET/POST | `/login`, `/register`, forgot/reset password | Breeze |
| GET/POST | `/portal/login` | `portal.login` |

### Admin & Management

Middleware umum modul: `auth`, `role:admin,management`, `readonly`

| Resource / path | Nama route |
|-----------------|------------|
| `/dashboard` | `dashboard` |
| `/members` | `members.*` |
| `/resi` | `resi.*` |
| `/transaksi` | `transaksi.*` |
| `/pendapatan-usaha` | `pendapatan-usaha.*` |
| `/pengeluaran` | `pengeluaran.*` |
| `/akad-jual-beli` | `akad-jual-beli.*` |
| `/members/{member}/akads-json` | `members.akads-json` |
| `/laporan/*` | `laporan.*` |
| `/profile` | `profile.*` (tanpa middleware readonly) |

Management: form create/edit masih bisa dibuka (GET), tetapi submit (POST/PUT/PATCH/DELETE) ditolak middleware `readonly`.

### Auth anggota

Lihat [bagian 10](#10-portal-anggota).

---

## 12. Seeder & data demo

`DatabaseSeeder` menjalankan berurutan:

1. `AdminSeeder`
2. `ManagementSeeder`
3. `MemberSeeder`
4. `ResiSeeder`
5. `TransaksiSeeder`
6. `TransaksiDetailSeeder`
7. `AkadJualBeliSeeder`
8. `PendapatanUsahaSeeder`
9. `PengeluaranKoperasiSeeder`

### Kredensial demo

| Akun | Kredensial |
|------|------------|
| Admin | `admin@koperasi.local` / `admin123` |
| Management | `management@koperasi.local` / `mgmt123` |
| Anggota (contoh) | `081511116987` / `koperasi123` |
| Target iuran default seeder | Rp 200.000 / bulan |
| Target simpanan pokok seeder | Rp 1.000.000 |

Anggota seeder: `KOP-001` … `KOP-010`.

> Jangan gunakan password seeder di production.

---

## 13. Console command

### `storage:cleanup-orphaned`

```bash
php artisan storage:cleanup-orphaned --dry-run
php artisan storage:cleanup-orphaned
```

Menghapus file di folder `bukti`, `pendapatan`, `pengeluaran` yang tidak direferensikan kolom bukti utama di database.

**Catatan:** command saat ini belum memindai folder `akad-bukti` dan kolom JSON `bukti_files`.

---

## 14. Aturan bisnis penting

1. **Saldo awal vs mutasi**  
   `members.simpanan_*` = baseline. Mutasi setelah go-live dihitung dari transaksi.

2. **Pemutihan simpanan wajib 2023**  
   Progress iuran wajib mengecualikan Januari–Desember 2023 sebagai masa pemutihan; progress dibatasi maks 100%.

3. **Nomor otomatis**  
   - Anggota: saran `KOP-####`  
   - Transaksi: `TRX-YYYYMM-###`  
   - Akad: `AKD-YYYYMM-###`

4. **Total transaksi selalu dari detail**  
   Create/update menghitung ulang `jumlah_total` dari baris detail.

5. **Link cicilan → akad**  
   Hanya jenis `cicilan_pembelian_barang` yang menautkan `akad_jual_beli_id`. Status akad bisa otomatis `lunas` jika lunas terbayar.

6. **Resi vs transaksi tunggal**  
   Resi = batch multi-anggota + bukti transfer. Transaksi langsung = satu anggota, resi opsional.

7. **Read-only management**  
   Semua mutasi data hanya boleh oleh `admin`.

8. **Isolasi data portal**  
   Anggota hanya melihat data sendiri; akses resi dicek kepemilikan transaksi.

---

## 15. Virtual host Laragon & Cloudflare Tunnel

### URL

| Mode | URL | Backend |
|------|-----|---------|
| Lokal | `http://koperasi-app.test` | Apache Laragon → `public/` |
| Publik | `https://koperasi.finnest.my.id` | Cloudflare Tunnel → Apache `:80` |

### Virtual host

File Laragon: `D:\laragon\etc\apache2\sites-enabled\auto.koperasi-app.test.conf`

- `ServerName`: `koperasi-app.test`
- `DocumentRoot`: `D:/laragon/www/koperasi-app/public`
- Hosts: `127.0.0.1 koperasi-app.test`

### Cloudflare Tunnel

Config default: `%USERPROFILE%\.cloudflared\config.yml`  
(Salinan aturan yang sama juga ada di `config-finnest.yml`.)

```yaml
ingress:
  - hostname: koperasi.finnest.my.id
    service: http://127.0.0.1:80
    originRequest:
      httpHostHeader: koperasi-app.test
  - service: http_status:404
```

`httpHostHeader` wajib agar Apache name-based vhost memilih site yang benar.

Jalankan (pakai `config.yml` default):

```bash
cloudflared tunnel run
```

Jika muncul peringatan *No ingress rules were defined*, biasanya `cloudflared` dijalankan tanpa file config — pastikan memakai `config.yml` di folder `.cloudflared`, atau:

```bash
cloudflared tunnel --config "%USERPROFILE%\.cloudflared\config.yml" run
```

Urutan start: **Laragon (Apache + MySQL)** → **cloudflared**. Tidak perlu `php artisan serve`.

### Laravel di belakang proxy

- `APP_URL=https://koperasi.finnest.my.id`
- `trustProxies(at: '*')` di `bootstrap/app.php`
- `URL::forceScheme('https')` di `AppServiceProvider` bila request HTTPS / `X-Forwarded-Proto: https`

---

## Lampiran: checklist setup lokal (Laragon)

1. Pastikan PHP 8.2+ & Composer aktif
2. `composer install` → salin `.env` → `php artisan key:generate`
3. Konfigurasi DB (SQLite atau MySQL di Laragon)
4. `php artisan migrate --seed`
5. `php artisan storage:link`
6. `npm install && npm run build` (atau `npm run dev`)
7. Start Laragon Apache → buka `http://koperasi-app.test/login` (pengurus) atau `/portal/login` (anggota)
8. (Opsional publik) jalankan `cloudflared` dengan `config-finnest.yml` → `https://koperasi.finnest.my.id`

---

*Dokumentasi ini mencerminkan struktur kode di repositori `koperasi-app` (Laravel 12 / FinNest).*
