5.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
An LMS / CBT (Computer-Based Test) application for employee training compliance, built on Laravel 13 with Livewire 3 + Volt. The codebase is a rewrite ("v2") of an older CodeIgniter 3 system; data is migrated in from the legacy database via Artisan commands (see Data Migration below). Most code comments and many domain terms are in Indonesian.
Commands
composer dev # Run the full dev stack concurrently: php artisan serve + queue:listen + pail (logs) + vite
npm run dev # Vite dev server only (HMR for assets)
npm run build # Production asset build
composer test # Clears config cache, then runs the full PHPUnit suite
php artisan test # Run tests directly
php artisan test --filter=AuthenticationTest # Run a single test class
php artisan test tests/Feature/Auth/PasswordResetTest.php # Run a single file
vendor/bin/pint # Format PHP (Laravel Pint, default ruleset — no config file)
Tests run against an in-memory SQLite database (configured in phpunit.xml), independent of the MySQL dev database.
Architecture
Routing & access control (read routes/web.php first)
Authorization is layered through nested route-group middleware, not per-controller checks:
auth— must be logged in.force.password(App\Http\Middleware\ForcePasswordChange, aliased inbootstrap/app.php) — ifUser->must_change_passwordis true, every request is redirected topassword.force-changeuntil the user sets a new password. This implements mandatory first-login password rotation for migrated accounts.- Spatie role middleware splits the app into areas:
role:admin|trainer→/admin(prefixadmin.) and/reports— management UI, master data, question bank, exports/imports.role:karyawan→/portal(route names prefixedcbt.) — the employee test-taking portal.
bootstrap/app.php is where middleware aliases and the global web middleware stack are registered (Laravel 11+ style — there is no Http/Kernel.php).
Three user roles
admin, trainer, karyawan (employee). Roles are managed by spatie/laravel-permission (role: middleware, HasRoles trait on User). Note: User also has a legacy role string column that older migration code writes to directly — prefer the Spatie role API for new authorization logic.
Volt page routing
VoltServiceProvider mounts both resources/views/livewire and resources/views/pages as Volt component roots. Auth screens (routes/auth.php, Laravel Breeze + Volt) and the password-reset flow are single-file Volt components under resources/views/pages/auth. Controllers in app/Http/Controllers/{Admin,Cbt} mostly return Blade views (resources/views/pages/...) that embed Livewire components for interactivity.
Domain model
- Master data:
Department⇄Position(many-to-many viadepartment_position); aUserbelongs to one department and one position. - TrainingMatrix ties a
Department+Position+Soptogether — it defines which SOP/training a role must complete (compliance requirement). - Sop — standard operating procedure / training material. Uses
spatie/laravel-activitylog(getActivitylogOptions). - Exam / QuestionBank / Question —
QuestionBankis scoped by department/position/training_matrix; exams produceExamResultrecords (is_passed,user_id,exam_id). - Compliance is computed (not stored):
App\Livewire\Matrix\ComplianceMonitorderives the pass rate askaryawanusers with ≥1 passingExamResult÷ totalkaryawan. UserDocument— uploaded employee documents.
When relating to User, note the legacy migration columns old_id and old_password_hash used to map/verify accounts carried over from the CI3 system.
Activity logging
Login/logout are logged via Event::listen in AppServiceProvider::boot() (category autentikasi). Models use spatie/activitylog for audit trails, surfaced through AuditTrailController.
File storage — Nextcloud over WebDAV
AppServiceProvider::boot() registers a custom webdav Storage driver (Sabre DAV client + Flysystem). The nextcloud disk in config/filesystems.php uses it, configured via NEXTCLOUD_* env vars. Use Storage::disk('nextcloud') for employee document/photo storage.
Imports / exports
Employee, department, and position data export to Excel (maatwebsite/excel, app/Exports) and PDF (barryvdh/laravel-dompdf). Employee import has a preview→process two-step flow (app/Imports, see EmployeeController routes). Custom export/import routes are declared before Route::resource(...) in web.php so they aren't shadowed by resource wildcards — preserve that ordering when adding routes.
Data Migration (legacy CI3 → v2)
A second MySQL connection mysql_old (env DB_*_OLD, default database lmsv2-old) holds the legacy data. The migration is run by php artisan lms:migrate-data (app/Console/Commands/MigrateLmsData.php), which orchestrates, in strict order: departments (from sections) → positions → department-position links → staff/students → user dept/position sync → questions → exam history. Additional one-off commands exist: lms:migrate-* (MigrateOldQuestions, MigrateStaffToTrainer, MigrateUserInitials, MigrateUserKtp). These read the old DB directly with DB::table('lmsv2-old.<table>').
Note: the repo is not a git repository. There is a stray file named
elect('SHOW COLUMNS FROM `lmsv2-old`.classes');in the project root — a scratch artifact, not source.