---
name: aai-pn-development
description: Panduan pengembangan proyek AAI-PN (Sistem Informasi Terpadu & Portal Resmi Pengurus Nasional Asosiasi Arsiparis Indonesia). Gunakan saat membuat atau memodifikasi fitur, modul Domain (DDD), komponen Livewire, model, service, repository, enum, migrasi, seeder, atau test pada codebase Laravel 11 + Livewire ini.
---

# AAI-PN Development Skill

Panduan konvensi dan alur kerja untuk mengembangkan aplikasi AAI-PN agar konsisten dengan arsitektur yang sudah ada. **Baca file ini sebelum menulis kode baru.**

## Ringkasan Proyek

Sistem Informasi Terpadu AAI-PN: portal publik/CMS, keanggotaan & KTA digital, sertifikasi LSP, event & diklat, e-voting, e-office (tata naskah), dan keuangan/iuran. UI berbahasa Indonesia; kode (nama class, method, kolom) berbahasa Inggris dengan istilah domain Indonesia di mana perlu (mis. `jenjang_arsiparis`, `golongan`).

## Tech Stack

- PHP 8.2+, Laravel 11 (`laravel/framework: ^11.31`)
- Livewire (komponen full-page, `app/Livewire/`)
- Tailwind CSS 3 + Vite 6 + Alpine.js (bawaan Livewire)
- Spatie: `laravel-permission`, `laravel-activitylog`, `laravel-medialibrary`
- Laravel Scout (search), Sanctum (API), Horizon (queue), Socialite (Google SSO)
- Maatwebsite Excel (import/export), barryvdh/laravel-dompdf (PDF), simple-qrcode (KTA/QR)
- Testing: PHPUnit 11; Linting: Laravel Pint

## Arsitektur: Domain-Driven Design

Seluruh logika bisnis berada di `app/Domain/{Module}/` dengan struktur seragam:

```
app/Domain/{Module}/
├── Models/        # Eloquent models
├── Repositories/  # Query & data access (XxxRepository extends EloquentRepository)
└── Services/      # Business logic (XxxService extends BaseService)
```

| Module | Isi utama |
|---|---|
| `Auth` | `User`, `LoginHistory`, `UserDevice`; roles via Spatie |
| `Membership` | `Member`, `MemberCard` (KTA), `MemberDocument`, `MemberCertificate`, `MemberStatusHistory` |
| `Finance` | `Due` (iuran), `Payment`, `Invoice` |
| `LSP` | `CertificationScheme`, `Assessor`, `Tuk`, `CertificationBatch`, `CertificationParticipant`, `CertificationDocument`, `LspCertificate` |
| `Event` | `Event`, `EventRegistration`, `EventAttendance`, `EventCertificate` |
| `Voting` | `Election`, `ElectionCandidate`, `ElectionVote`, `ElectionAuditLog` |
| `Correspondence` | `LetterIn`, `LetterOut`, `LetterNumber` (penomoran otomatis) |
| `CMS` | `Article`, `ArticleCategory`, `Agenda`, `Gallery`, `GalleryItem`, `Banner`, `Faq`, `Menu` |
| `Organization` | `Region`, `Institution`, `OrganizationUnit`, `OrganizationPeriod`, `OrganizationMember` |
| `Core` | `Setting`, `AuditLog` |

Shared utilities: `app/Support/` berisi `Enums/`, `Repositories/` (`RepositoryInterface`, `EloquentRepository`), dan `Services/BaseService`. Base model: `app/Models/BaseModel` (abstract, menyediakan `$perPage = 15`, scope `active`, `search`, `dateBetween`).

## Konvensi Kode

### Models

- **Selalu** `extends App\Models\BaseModel` (jangan `Model` langsung), kecuali `User` yang extends `Illuminate\Foundation\Auth\User`.
- Definisikan relasi + `use LogsActivity, SoftDeletes` bila tabel memiliki `deleted_at`.
- Cast enum/date via method `protected function casts(): array` menggunakan enum class (contoh: `'status' => MemberStatus::class`).
- Daftarkan kolom pencarian di `protected array $searchable = [...]` agar scope `search()` bekerja.
- Tambahkan `scope` untuk filter yang sering dipakai (contoh: `scopePending`, `scopeActive`, `scopeByRegion`).
- Activity log: implementasi `getActivitylogOptions()` dengan `logOnly([...])->logOnlyDirty()->dontSubmitEmptyLogs()`.
- Model contoh referensi: `app/Domain/Membership/Models/Member.php` — ikuti polanya.

### Enums

Semua status/tipe sebagai **backed enum** di `app/Support/Enums/` dengan dua method wajib:

```php
public function label(): string  { /* label bahasa Indonesia */ }
public function color(): string  { /* green|yellow|red|gray|orange */ }
```

Contoh: `MemberStatus`, `PaymentStatus`, `ArticleStatus`, `ElectionStatus`, `UserStatus`, `RegionType`.

### Services

- `extends App\Support\Services\BaseService`.
- Bungkus operasi multi-tabel dengan `$this->transactional(function () { ... }, 'Pesan error')` — sudah menangani `DB::transaction` + logging.
- Inject repository / service lain via constructor promotion. Contoh referensi: `MembershipService::approveMember()` yang membuat nomor anggota, KTA, dan iuran awal sekaligus.

### Repositories

- `extends App\Support\Repositories\EloquentRepository`; panggil `parent::__construct($model)`.
- Gunakan method bawaan: `query()`, `all()`, `paginate()`, `find()`, `findOrFail()`, `findBy()`, `create()`, `update()`, `delete()`.
- Tambahkan method spesifik domain yang mengembalikan query terfilter (contoh: `paginateVerifiedMembers()`, `findByMemberNumber()`).

### Livewire Components

- Namespace & folder harus sinkron:
  - `App\Livewire\Admin\{SubModule}\*` → layout admin
  - `App\Livewire\Portal\*` → layout portal (self-service anggota)
  - `App\Livewire\Front\*` → halaman publik
  - `App\Livewire\Auth\*` → login/register
- Tandai layout dengan atribut: `#[Layout('layouts.admin')]` (tersedia: `layouts.admin`, `layouts.portal`, `layouts.front`).
- View blade di `resources/views/livewire/{kebab-path}/...` (mis. `verification-queue.blade.php`).
- Inject service langsung di method action (contoh: `public function approve(int $memberId, MembershipService $membershipService)`).
- Gunakan `WithPagination` + `paginate(10)` untuk daftar data; `wire:model.live` untuk filter.
- Pesan ke user dalam bahasa Indonesia (mis. `$this->noticeMessage = "Sukses! ...";`).

### Routes

- `routes/web.php` hanya me-`require` file turunan: `front.php`, `admin.php` (ditambah API di `api.php`).
- Rute publik: grup `Route::name('front.')` di `routes/front.php`.
- Rute admin: grup `Route::prefix('admin')->name('admin.')->middleware(['auth', CheckAdminAccess::class])` di `routes/admin.php`.
- Placeholder boleh `fn () => ''` untuk halaman yang belum diimplementasikan — pertahankan polanya saat menambah route baru.
- Rute anggota: prefix `portal`, name `portal.`, middleware `auth` (lihat `routes/web.php`).

### RBAC (Spatie Permission)

- Permission memakai format `module.resource.action` (mis. `members.approve`, `finance.payments.verify`, `lsp.batches.create`).
- `super-admin` bypass semua gate via `Gate::before` di `app/Providers/DomainServiceProvider.php`.
- Middleware `CheckAdminAccess` memblokir user dengan role `anggota`/`guest` dari area admin.
- Role yang tersedia (lihat `database/seeders/RolePermissionSeeder.php`): `super-admin`, `administrator`, `sekretariat-nasional`, `bendahara-nasional`, `ketua-umum`, `pengurus-pusat`, `pengurus-wilayah`, `pengurus-cabang`, `lsp-admin`, `verifier-anggota`, `anggota`, `guest`.
- Saat menambah fitur admin baru: tambahkan permission di `RolePermissionSeeder`, lalu `givePermissionTo` role terkait.

### Penomoran & Verifikasi

- Nomor anggota diformat `AAI.{KodeProvinsi}.{Tahun}.{Sequence}` via `MembershipService::generateMemberNumber()`.
- Data pribadi (NIK/NIP/telepon) wajib dimasking saat tampil di halaman verifikasi publik memakai `MembershipService::maskPersonalData()`.
- QR code KTA memakai `simple-qrcode`; verifikasi via `VerificationController` (rute `front.card.verify`, `front.membership.verify`, `front.certification.verify`).

## Database, Migrasi & Seeder

- Database dev memakai SQLite (`database/database.sqlite`); test juga SQLite (lihat `phpunit.xml`).
- Reset penuh + data dummy: `php artisan migrate:fresh --seed`.
- Seeder dijalankan dalam urutan dependency (lihat `DatabaseSeeder`): `RolePermissionSeeder` → `OrganizationSeeder` → `MembershipSeeder` → `FinanceSeeder` → `ContentAndEventSeeder` → `MenuSeeder` → `LspSeeder` → `VotingAndCorrespondenceSeeder` → `SettingSeeder`.
- Saat menambah seeder baru, daftarkan di `DatabaseSeeder` pada posisi yang benar.
- **Kredensial testing** (semua password: `password`):
  - `superadmin@aai.or.id` (super-admin, akses penuh)
  - `admin@aai.or.id` (administrator)
  - `ketum@aai.or.id`, `sekjen@aai.or.id`, `bendahara@aai.or.id` (pengurus pusat)
  - `fauzi.arsip@gmail.com`, `budi.santoso@bca.co.id` (anggota)
- Environment penting: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI` untuk SSO Google.

## Testing & Code Style

- Test ada di `tests/Feature/` (mayoritas) dan `tests/Unit/`; base class `Tests\TestCase` sudah memanggil `withoutVite()`.
- Gunakan `use RefreshDatabase;` untuk test yang menyentuh database.
- Pola assertion domain: buat record via `Model::create([...])` atau factory, lalu assert relasi/scope/method helper (lihat `tests/Feature/MembershipDomainTest.php`).
- WAJIB menjalankan sebelum commit:
  ```bash
  ./vendor/bin/pint          # perbaiki gaya kode (CI memakai --test)
  php artisan test           # seluruh test suite
  ```
- CI (`ci.yml`) berjalan di setiap push/PR ke `main`: composer audit, `pint --test`, dan `php artisan test` dengan SQLite.

## Deployment (CI/CD)

- Push/merge ke `main` otomatis deploy ke cPanel `dev.arsiparis.org` via GitHub Actions (`deploy.yml`).
- Alur: build assets (`npm run build`) → kompres `tar.gz` (tanpa `.env`, `storage/`, `tests/`) → upload SCP → ekstrak & `php artisan optimize:clear && php artisan migrate --force` via SSH.
- Secrets GitHub yang dipakai: `SSH_HOST`, `SSH_USERNAME`, `SSH_PORT`, `SSH_PRIVATE_KEY`.
- Jangan pernah meng-commit `.env`, kredensial, atau file build hasil deploy (`deploy.tar.gz`).

## Checklist Menambah Fitur Baru (mengikuti alur yang ada)

1. Tentukan module Domain yang tepat (atau buat folder baru mengikuti struktur `Models/Repositories/Services`).
2. Buat/ubah Enum di `app/Support/Enums/` bila ada status baru.
3. Buat migrasi + model (`extends BaseModel`, casts, `$searchable`, relasi, scope, activity log).
4. Buat `Repository` (extends `EloquentRepository`) dan `Service` (extends `BaseService`, gunakan `transactional()`).
5. Buat komponen Livewire di namespace yang tepat + view blade-nya dengan layout sesuai area.
6. Daftarkan rute (dan permission baru di `RolePermissionSeeder` bila area admin).
7. Tambahkan seeder/dummy data bila perlu (dan daftarkan di `DatabaseSeeder`).
8. Tulis test Feature/Unit dengan `RefreshDatabase`.
9. Jalankan `./vendor/bin/pint` dan `php artisan test` sampai hijau.

## Perintah Berguna

```bash
composer dev                                # serve + queue + pail + vite (all-in-one)
php artisan migrate:fresh --seed            # reset DB + dummy data
php artisan test --filter=NamaTest          # test spesifik
./vendor/bin/pint --dirty                   # format hanya file yang berubah
php artisan optimize:clear                  # bersihkan cache (dipakai saat deploy)
npm run dev / npm run build                 # asset Vite
php artisan sitemap:generate                # generate sitemap.xml dari konten published
php artisan cache:flush-front               # flush cache frontend (menu, homepage, artikel)
```

