# AGENTS.md

Context for OpenCode agents working in **SIMKATMAWA** (Sistem Informasi Manajemen Kegiatan Kemahasiswaan) — a Laravel 12 app for managing **SKPI** (Surat Keterangan Pendamping Ijazah) verification of student achievements. Domain docs (Indonesian): `docs/simkatmawa_analysis.md`, `docs/use_case.png`, `docs/user_flow.png`.

## Stack

- Laravel 12, PHP **8.2+**, MySQL (default conn).
- Frontend: Blade + Bootstrap 5 + Tailwind 4, bundled by Vite (`resources/sass/app.scss`, `resources/js/app.js`).
- Auth via `laravel/ui` (not Breeze/Sanctum). Roles/permissions via `spatie/laravel-permission` + a custom `permission_groups` table. Impersonation via `lab404/laravel-impersonate`.
- Tables: `yajra/laravel-datatables-oracle`. PDF: `barryvdh/laravel-dompdf` + `spatie/browsershot` (Node/Puppeteer). QR: `simplesoftwareio/simple-qrcode`.
- Some models use **ULID** PKs (`Pegawai`, `PrestasiMandiri` and its FKs).

## Common commands

- Dev (all-in-one): `composer dev` → runs `php artisan serve` + `queue:listen` + `pail` logs + `vite` concurrently. Prefer this over `php artisan serve` alone, else queue/Vite won't run.
- Tests: `composer test` (= `artisan config:clear` + `artisan test`), or `php artisan test`.
- Single test: `php artisan test --filter=PrestasiMandiriTest` / `--filter=test_admin_can_create_prestasi_mandiri`.
- Formatter: `vendor/bin/pint` (PSR-12-ish; follow existing style, no new comments).
- Migrations: `php artisan migrate`. Migration timestamps run into 2026 by design — don't "fix" them.
- Generated, do **not** edit: `_ide_helper.php`, `_ide_helper_models.php` (from `barryvdh/laravel-ide-helper`).

## Three databases (critical)

`config/database.php` defines 3 MySQL connections. Models pin one via `protected $connection`:

| Connection | Purpose | Models (selected) |
|---|---|---|
| `mysql` (default) | App-owned data: users, permissions, prestasi_mandiri*, akses_sertifikat, master_dekan | `User`, `PrestasiMandiri*`, `AksesSertifikat`, `MasterDekan`, `UserAksesProdi`, `PermissionGroup` |
| `mysql_siak` | External SIAK academic DB (read-mostly). Tables e.g. `skpi_kegiatan_mhs`, `mahasiswa`, `prodi`, `kegiatan`, `komponen_kegiatan`, `bobot_kegiatan`, `akreditasi`, `tahun_akademik_siak` | `Kegiatan*`, `Mahasiswa`, `Prodi`/`Programstudi`, `NamaPrestasi`, `TingkatKegiatan`, `KomponenKegiatan`, `BobotKegiatan`, `Akreditasi`, `StatusMahasiswa`, `TahunAkademikSiak` |
| `mysql_neosisdam` | External Neosisdam SSO + HR | `Pegawai`, `UserPegawai`, `UnitKerja`, `SubUnitKerja`, `Riwayathomebase` |

- **No migrations are owned for `mysql_siak` / `mysql_neosisdam`** — their schema is external. Only the default `mysql` connection is migrated here.
- `.env` must set `DB_*`, `SIAK_DB_*`, and `NEOSISDAM_DB_*`. Missing SIAK/Neosisdam creds → most pages + login (SSO) break.
- Many controllers query SIAK via `DB::connection('mysql_siak')->table('skpi_kegiatan_mhs')` directly (not Eloquent) — check `HasilSkorKegiatanMahasiswaController`, `AksesSertifikatSkpiController`, `PencatatanKegiatanMhsController`, `MasterDekanController`, `SertifyAuthenticityCheck`.

## External services (env required, else runtime exceptions)

- `NEOSISDAM_*` — OAuth SSO (`OauthController`, login flow). Callback creates/updates `User` with `origin='neosisdam'` + `origin_uid`.
- `DIKTI_PRESTASI_MANDIRI_*` — push prestasi to Kemdiktisaintek. See `app/Services/DiktiPrestasiMandiriClient.php` + `PrestasiMandiriSyncService.php`. Routes: `prestasi/prestasi-mandiri/{sync,sync-bulk}`.
- `FILE_STORAGE_SERTIFIKAT_KEMAH_*` — file upload API (`FileUploadService`, throws if unset).
- PDF via **Browsershot needs Node + Puppeteer** installed (`puppeteer` is a `package.json` dep → `npm install`).

## Testing quirks

- `phpunit.xml` forces in-memory **SQLite** and overrides most drivers. External-DB code is **not** exercised by tests.
- Tests do **not** run migrations and do **not** use `RefreshDatabase`. Each feature test self-builds its schema inline (see `PrestasiMandiriTest::setUpDatabase()`). When adding tests, follow that pattern — define only the tables you touch.
- `tests/Feature/ExampleTest.php` hits `/` (renders `auth.login`); needs Blade views present.
- Factories exist but are barely used; check `database/factories/` before assuming one exists.

## Conventions

- Global helper `tahunakademikaktif()` (cached active academic year from SIAK) is autoloaded via `composer.json` `autoload-dev.files` → `app/Helpers/myHelpers.php`. Available globally, no `use` needed.
- Route names are lowercase-with-dashes / Indonesian terms (e.g. `namaprestasi.storeOrUpdate`, `inventariskegiatanmhs.index`). Controllers use `storeOrUpdate`/`getData`/`edit`/`update`/`destroy` action names rather than resource defaults, except `prestasi-mandiri` (real resource controller).
- Observers registered manually in `app/Providers/AppServiceProvider.php` (`KegiatanMahasiswaObserver`, `UserAksesProdiObserver`) — not auto-discovered.
- Impersonation: only `developer` role can impersonate; nobody can impersonate a `developer` (see `User::canImpersonate`/`canBeImpersonated`).
- Indonesian UI/DB naming throughout. Keep Indonesian terms in routes/columns; code identifiers stay English.

## Route map (prefixes under `auth` middleware)

`developer/` (users, roles, permissions, permission-groups, user-akses-prodi), `master/` (nama-prestasi, tingkat-kegiatan, komponen-kegiatan, kegiatan, bobot-kegiatan, akreditasi, syarat-total-nilai-bobot), `verifikasi/` (tingkat-universitas, tingkat-program-studi), `pencatatan-kegiatan-mahasiswa/`, `hasil-nilai-kegiatan-mahasiswa/` (incl. SKPI preview/pdf/verifikasi), `akses-sertifikat-skpi/`, `master-dekan/`, `prestasi/prestasi-mandiri` (resource). Public: `/`, `/home`, Neosisdam OAuth, `verifikasi-skpix/{nim}`.

## Deployment

- `master` deploys automatically: `bitbucket-pipelines.yml` runs `deploy/production.sh` on a self-hosted Linux Shell runner (`server.neorpl`) installed on the production server. Steps: maintenance mode → `git reset --hard` to the deployed commit → `composer install --no-dev` → `migrate --force` → `optimize`.
- Production runs the app under Apache **mod_php 8.2**, while the CLI (`php`, used by the pipeline) is **8.3** — keep `composer.lock` resolvable on 8.2 or the site breaks even though the pipeline passes.
- Vite assets are not built during deploy: the live layout (`layouts/main.blade.php`) uses static files from `public/assets`, and `@vite` only appears in the unused `layouts/app.blade.php`. Wiring `@vite` into a live layout means adding Node + a build step to the pipeline first.

## Notes

- `app/Http/Controllers/HomeController2.php` and some commented blocks exist — verify which is live before editing; `HomeController` is wired in `routes/web.php`.
- `.env.example` is the source of truth for required env keys; it diverges from `config/services.php` (e.g. no `NEOSISDAM_DB_*` rows) — trust `config/database.php` + `config/services.php` for actual keys.
