Files
lms-v2/CLAUDE.md
T
2026-05-30 22:15:16 +07:00

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:

  1. auth — must be logged in.
  2. force.password (App\Http\Middleware\ForcePasswordChange, aliased in bootstrap/app.php) — if User->must_change_password is true, every request is redirected to password.force-change until the user sets a new password. This implements mandatory first-login password rotation for migrated accounts.
  3. Spatie role middleware splits the app into areas:
    • role:admin|trainer/admin (prefix admin.) and /reports — management UI, master data, question bank, exports/imports.
    • role:karyawan/portal (route names prefixed cbt.) — 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: DepartmentPosition (many-to-many via department_position); a User belongs to one department and one position.
  • TrainingMatrix ties a Department + Position + Sop together — it defines which SOP/training a role must complete (compliance requirement).
  • Sop — standard operating procedure / training material. Uses spatie/laravel-activitylog (getActivitylogOptions).
  • Exam / QuestionBank / QuestionQuestionBank is scoped by department/position/training_matrix; exams produce ExamResult records (is_passed, user_id, exam_id).
  • Compliance is computed (not stored): App\Livewire\Matrix\ComplianceMonitor derives the pass rate as karyawan users with ≥1 passing ExamResult ÷ total karyawan.
  • 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.