commit f01cfe71523267a7990112f73d3e63bc5328bbe5 Author: Elsaaaa13 <124854502+Elsaaaa13@users.noreply.github.com> Date: Mon Jun 15 14:12:34 2026 +0700 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a186cd2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/.env b/.env new file mode 100644 index 0000000..1bf6b8c --- /dev/null +++ b/.env @@ -0,0 +1,64 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY=base64:JCHuHMTGBSdSPEnmKhtW4iMbEy34yaBrHK8T9pX6r7k= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=pencatatanobat2 +DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=smtp-relay.brevo.com +MAIL_PORT=587 +MAIL_USERNAME=aa5d12001@smtp-brevo.com +MAIL_PASSWORD=xsmtpsib-dfa58e574c1f22cc4275cfbc4be470e6a1ed321485af02d908fa26d7708ba3ad-FmgHA9sAQoojTdH9 +MAIL_FROM_ADDRESS="mbakgopie@gmail.com" +MAIL_FROM_NAME="MedData Puskesmas" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c0660ea --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcb21d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..436eec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +*.log +.DS_Store +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.fleet +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +Homestead.json +Homestead.yaml +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..75be56e --- /dev/null +++ b/README.md @@ -0,0 +1,213 @@ +# MedData โ€” Sistem Pendataan Obat Puskesmas + +Aplikasi web untuk pencatatan, pengelolaan, dan monitoring obat di Puskesmas. Dilengkapi dengan manajemen resep, laporan, monitoring kadaluarsa, dan dashboard interaktif. + +![Laravel](https://img.shields.io/badge/Laravel-12-red?logo=laravel) +![PHP](https://img.shields.io/badge/PHP-8.2+-blue?logo=php) +![MySQL](https://img.shields.io/badge/MySQL-8.0+-orange?logo=mysql) +![Tests](https://img.shields.io/badge/Tests-159%20passed-brightgreen?logo=phpunit) + +--- + +## ๐Ÿ“‹ Fitur Utama + +### ๐Ÿ  Dashboard +- Statistik: total jenis obat, obat masuk/keluar bulan ini, obat mendekati kadaluarsa +- Grafik tren harian obat masuk vs keluar (filter per bulan/tahun) +- Pie chart distribusi obat masuk (top 5) +- Notifikasi real-time obat mendekati kadaluarsa + +### ๐Ÿ“ฅ Obat Masuk +- CRUD lengkap data penerimaan obat +- Field: nama obat, kode batch, kategori, satuan, supplier, stok, harga, tanggal penerimaan & kadaluarsa +- Search dan filter status stok (tersedia/habis) +- Chart distribusi obat per bulan + +### ๐Ÿ“ค Obat Keluar +- Pencatatan pengeluaran obat terhubung ke data obat masuk +- Pemotongan stok otomatis saat pengeluaran, pemulihan saat dihapus +- Validasi stok tidak boleh melebihi yang tersedia +- Filter berdasarkan status, tujuan pemakaian, barcode + +### ๐Ÿ“ Resep +- Pembuatan resep oleh Dokter (multi-item per resep) +- Stok obat otomatis berkurang saat resep dibuat +- Alur status: `proses` โ†’ `selesai` / `dibatalkan` (dikonfirmasi Apoteker) +- Update status resep sekaligus memperbarui status Obat Keluar terkait +- No. resep otomatis: `RSP-YYYYMMDD-NNNN` + +### โฐ Monitoring Kadaluarsa +- Tampilan obat yang sudah/mendekati kadaluarsa (โ‰ค 60 hari) +- Filter: Semua, Sudah Kadaluarsa, Awas (โ‰ค 30 hari), Waspada (โ‰ค 60 hari) +- Badge status berwarna (Aman / Waspada / Awas) + +### ๐Ÿ“Š Laporan +- Laporan obat masuk dan keluar +- Filter berdasarkan jenis dan rentang tanggal +- Export ke **PDF** (DomPDF) dan **Excel** (Maatwebsite) + +### ๐Ÿ—‚๏ธ Master Data +- Manajemen Kategori Obat +- Manajemen Satuan Obat + +### ๐Ÿ‘ค Profil +- Update profil: nama, email, NIP, gender, nomor telepon, divisi, jabatan, alamat +- Ubah password +- Upload foto profil + +--- + +## ๐Ÿ” Sistem Peran (Role) + +| Role | Akses | +|------|-------| +| **Dokter** | Buat/edit/hapus Resep | +| **Apoteker** | Obat Masuk, Obat Keluar, konfirmasi status Resep, Laporan, Master Data | +| **Semua** | Dashboard, Monitoring Kadaluarsa, lihat Resep, Profil | + +--- + +## ๐Ÿ› ๏ธ Tech Stack + +| Komponen | Teknologi | +|----------|-----------| +| Backend | Laravel 12, PHP 8.2+ | +| Frontend | Blade Templates, Tailwind CSS v4, Alpine.js | +| Grafik | Chart.js | +| Database | MySQL 8.0+ | +| Export | DomPDF, Maatwebsite Excel | +| Testing | PHPUnit (159 tests) | + +--- + +## โš™๏ธ Instalasi + +### Prasyarat +- PHP โ‰ฅ 8.2 +- Composer +- Node.js โ‰ฅ 18 +- MySQL โ‰ฅ 8.0 + +### Langkah Instalasi + +```bash +# 1. Clone repository +git clone +cd pencatatanobat + +# 2. Install dependencies +composer install +npm install + +# 3. Konfigurasi environment +cp .env.example .env +php artisan key:generate +``` + +**4. Konfigurasi database di `.env`:** +```env +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=pencatatanobat +DB_USERNAME=root +DB_PASSWORD= +``` + +```bash +# 5. Migrasi & seeder +php artisan migrate --seed + +# 6. Build assets +npm run build + +# 7. Jalankan server +php artisan serve +``` + +Akses aplikasi di `http://127.0.0.1:8000` + +--- + +## ๐Ÿ”‘ Akun Default + +| Email | Password | Role | +|-------|----------|------| +| `dokter@dokter.com` | `password` | Dokter | +| `admin@admin.com` | `password` | Apoteker | +| `superadmin@meddata.com` | `password` | Superadmin | + +--- + +## ๐Ÿงช Testing + +Aplikasi memiliki **159 test** (unit + feature) yang mencakup semua controller dan model utama. + +```bash +# Jalankan semua test +php artisan test + +# Hanya unit test +php artisan test --testsuite=Unit + +# Hanya feature test +php artisan test --testsuite=Feature +``` + +> **Catatan:** Test menggunakan SQLite in-memory via custom `DatabaseTestCase` untuk menghindari konflik dengan migrasi MySQL-specific. + +--- + +## ๐Ÿ“ Struktur Direktori + +``` +pencatatanobat/ +โ”œโ”€โ”€ app/ +โ”‚ โ”œโ”€โ”€ Exports/ # Export Excel (Maatwebsite) +โ”‚ โ”œโ”€โ”€ Http/ +โ”‚ โ”‚ โ”œโ”€โ”€ Controllers/ # DashboardController, ObatMasuk, ObatKeluar, +โ”‚ โ”‚ โ”‚ # Resep, Kadaluarsa, Laporan, Kategori, Satuan +โ”‚ โ”‚ โ””โ”€โ”€ Middleware/ +โ”‚ โ”‚ โ””โ”€โ”€ CheckRole.php # Middleware role-based access +โ”‚ โ””โ”€โ”€ Models/ # User, ObatMasuk, ObatKeluar, Resep, +โ”‚ # ResepItem, Kategori, Satuan +โ”œโ”€โ”€ database/ +โ”‚ โ”œโ”€โ”€ migrations/ # Migrasi MySQL +โ”‚ โ”œโ”€โ”€ migrations/safe/ # Migrasi SQLite-compatible (untuk testing) +โ”‚ โ””โ”€โ”€ seeders/ +โ”œโ”€โ”€ resources/views/ +โ”‚ โ”œโ”€โ”€ dashboard/ +โ”‚ โ”œโ”€โ”€ obat-masuk/ +โ”‚ โ”œโ”€โ”€ obat-keluar/ +โ”‚ โ”œโ”€โ”€ resep/ +โ”‚ โ”œโ”€โ”€ kadaluarsa/ +โ”‚ โ”œโ”€โ”€ laporan/ +โ”‚ โ”œโ”€โ”€ kategori/ +โ”‚ โ”œโ”€โ”€ satuan/ +โ”‚ โ””โ”€โ”€ layouts/ +โ”œโ”€โ”€ routes/ +โ”‚ โ””โ”€โ”€ web.php +โ””โ”€โ”€ tests/ + โ”œโ”€โ”€ DatabaseTestCase.php # Base class dengan SQLite-compatible schema + โ”œโ”€โ”€ Unit/ # Model unit tests + โ””โ”€โ”€ Feature/ # Controller feature tests +``` + +--- + +## ๐ŸŽจ Skema Warna + +| Komponen | Hex | +|----------|-----| +| Sidebar | `#4A538F` | +| Background Konten | `#F4F6FF` | +| Teks Utama | `#2F347A` | +| Badge Aman | `#1F9254` | +| Badge Waspada | `#B78103` | +| Badge Awas | `#C0392B` | + +--- + +## ๐Ÿ“ Lisensi + +Proyek ini dikembangkan untuk keperluan internal Puskesmas Doko. diff --git a/app/Exports/ObatKeluarExport.php b/app/Exports/ObatKeluarExport.php new file mode 100644 index 0000000..79fd3ce --- /dev/null +++ b/app/Exports/ObatKeluarExport.php @@ -0,0 +1,55 @@ +startDate = $startDate; + $this->endDate = $endDate; + } + + public function collection() + { + return DB::table('obat_keluars') + ->join('obat_masuks', 'obat_keluars.obat_masuk_id', '=', 'obat_masuks.id') + ->leftJoin('satuans', 'obat_masuks.satuan_id', '=', 'satuans.id') + ->whereNotIn('obat_keluars.status', ['proses', 'dibatalkan']) + ->whereBetween('obat_keluars.tanggal_pengeluaran', [$this->startDate, $this->endDate]) + ->select( + 'obat_keluars.nama_obat', + 'satuans.nama as satuan', + DB::raw('SUM(obat_keluars.jumlah) as total_jumlah') + ) + ->groupBy('obat_keluars.nama_obat', 'satuans.nama') + ->orderBy('obat_keluars.nama_obat') + ->get(); + } + + public function headings(): array + { + return [ + 'Nama Obat', + 'Satuan', + 'Total Jumlah Keluar', + ]; + } + + public function map($row): array + { + return [ + $row->nama_obat ?? 'N/A', + $row->satuan ?? '-', + $row->total_jumlah, + ]; + } +} diff --git a/app/Exports/ObatMasukExport.php b/app/Exports/ObatMasukExport.php new file mode 100644 index 0000000..4665b1c --- /dev/null +++ b/app/Exports/ObatMasukExport.php @@ -0,0 +1,53 @@ +startDate = $startDate; + $this->endDate = $endDate; + } + + public function collection() + { + return DB::table('obat_masuks') + ->join('satuans', 'obat_masuks.satuan_id', '=', 'satuans.id') + ->whereBetween('tanggal_penerimaan', [$this->startDate, $this->endDate]) + ->select( + 'obat_masuks.nama_obat', + 'satuans.nama as satuan', + DB::raw('SUM(obat_masuks.stok) as total_jumlah') + ) + ->groupBy('obat_masuks.nama_obat', 'satuans.nama') + ->orderBy('obat_masuks.nama_obat') + ->get(); + } + + public function headings(): array + { + return [ + 'Nama Obat', + 'Satuan', + 'Total Jumlah Masuk', + ]; + } + + public function map($row): array + { + return [ + $row->nama_obat ?? 'N/A', + $row->satuan ?? '-', + $row->total_jumlah, + ]; + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php new file mode 100644 index 0000000..613bcd9 --- /dev/null +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -0,0 +1,47 @@ +authenticate(); + + $request->session()->regenerate(); + + return redirect()->intended(route('dashboard', absolute: false)); + } + + /** + * Destroy an authenticated session. + */ + public function destroy(Request $request): RedirectResponse + { + Auth::guard('web')->logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return redirect('/'); + } +} diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php new file mode 100644 index 0000000..712394a --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmablePasswordController.php @@ -0,0 +1,40 @@ +validate([ + 'email' => $request->user()->email, + 'password' => $request->password, + ])) { + throw ValidationException::withMessages([ + 'password' => __('auth.password'), + ]); + } + + $request->session()->put('auth.password_confirmed_at', time()); + + return redirect()->intended(route('dashboard', absolute: false)); + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php new file mode 100644 index 0000000..f64fa9b --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php @@ -0,0 +1,24 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended(route('dashboard', absolute: false)); + } + + $request->user()->sendEmailVerificationNotification(); + + return back()->with('status', 'verification-link-sent'); + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php new file mode 100644 index 0000000..ee3cb6f --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationPromptController.php @@ -0,0 +1,21 @@ +user()->hasVerifiedEmail() + ? redirect()->intended(route('dashboard', absolute: false)) + : view('auth.verify-email'); + } +} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php new file mode 100644 index 0000000..e8368bd --- /dev/null +++ b/app/Http/Controllers/Auth/NewPasswordController.php @@ -0,0 +1,62 @@ + $request]); + } + + /** + * Handle an incoming new password request. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function store(Request $request): RedirectResponse + { + $request->validate([ + 'token' => ['required'], + 'email' => ['required', 'email'], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function (User $user) use ($request) { + $user->forceFill([ + 'password' => Hash::make($request->password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $status == Password::PASSWORD_RESET + ? redirect()->route('login')->with('status', __($status)) + : back()->withInput($request->only('email')) + ->withErrors(['email' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php new file mode 100644 index 0000000..6916409 --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -0,0 +1,29 @@ +validateWithBag('updatePassword', [ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', Password::defaults(), 'confirmed'], + ]); + + $request->user()->update([ + 'password' => Hash::make($validated['password']), + ]); + + return back()->with('status', 'password-updated'); + } +} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php new file mode 100644 index 0000000..bf1ebfa --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordResetLinkController.php @@ -0,0 +1,44 @@ +validate([ + 'email' => ['required', 'email'], + ]); + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $status = Password::sendResetLink( + $request->only('email') + ); + + return $status == Password::RESET_LINK_SENT + ? back()->with('status', __($status)) + : back()->withInput($request->only('email')) + ->withErrors(['email' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php new file mode 100644 index 0000000..0739e2e --- /dev/null +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -0,0 +1,50 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + event(new Registered($user)); + + Auth::login($user); + + return redirect(route('dashboard', absolute: false)); + } +} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php new file mode 100644 index 0000000..784765e --- /dev/null +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -0,0 +1,27 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } + + if ($request->user()->markEmailAsVerified()) { + event(new Verified($request->user())); + } + + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..8677cd5 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,8 @@ +get('chart_month', now()->month); + $filterYear = $request->get('chart_year', now()->year); + + // Create date object for the selected month + $selectedDate = Carbon::createFromDate($filterYear, $filterMonth, 1); + + // Statistics + $totalJenisObat = ObatMasuk::count(); + $obatMasukBulanIni = ObatMasuk::whereMonth('tanggal_penerimaan', now()->month) + ->whereYear('tanggal_penerimaan', now()->year) + ->count(); + $obatKeluarBulanIni = ObatKeluar::whereMonth('tanggal_pengeluaran', now()->month) + ->whereYear('tanggal_pengeluaran', now()->year) + ->count(); + $kadaluarsaDekat = ObatMasuk::where('tanggal_kadaluarsa', '<=', now()->addMonths(4)) + ->where('tanggal_kadaluarsa', '>=', now()) + ->count(); + + // Chart data - daily data for selected month (line chart) + $daysInMonth = $selectedDate->daysInMonth; + $labels = []; + $obatMasukData = []; + $obatKeluarData = []; + + for ($i = 1; $i <= $daysInMonth; $i++) { + $date = Carbon::create($filterYear, $filterMonth, $i); + $labels[] = $i; + + $obatMasukData[] = ObatMasuk::whereDate('tanggal_penerimaan', $date)->count(); + $obatKeluarData[] = ObatKeluar::whereDate('tanggal_pengeluaran', $date)->count(); + } + + // Generate month options for filter (last 12 months) + $monthOptions = []; + for ($i = 0; $i < 12; $i++) { + $date = now()->subMonths($i); + $monthOptions[] = [ + 'value' => $date->format('Y-m'), + 'label' => $date->translatedFormat('F Y'), + 'month' => $date->month, + 'year' => $date->year, + ]; + } + + // Expiring medicines + $obatKadaluarsa = ObatMasuk::where('tanggal_kadaluarsa', '<=', now()->addMonths(4)) + ->with('obat') + ->orderBy('tanggal_kadaluarsa', 'asc') + ->limit(10) + ->get(); + + return view('dashboard.index', compact( + 'totalJenisObat', + 'obatMasukBulanIni', + 'obatKeluarBulanIni', + 'kadaluarsaDekat', + 'labels', + 'obatMasukData', + 'obatKeluarData', + 'obatKadaluarsa', + 'monthOptions', + 'filterMonth', + 'filterYear', + 'selectedDate' + )); + } +} + diff --git a/app/Http/Controllers/KadaluarsaController.php b/app/Http/Controllers/KadaluarsaController.php new file mode 100644 index 0000000..fdfa729 --- /dev/null +++ b/app/Http/Controllers/KadaluarsaController.php @@ -0,0 +1,34 @@ +where('tanggal_kadaluarsa', '<=', now()->addMonths(4)); + + // Filter by remaining days + $filter = $request->get('filter', 'all'); + + if ($filter === 'expired') { + // Only expired (minus days) + $query->where('tanggal_kadaluarsa', '<', now()); + } elseif ($filter === '30') { + // Awas: expired or <= 30 days remaining + $query->where('tanggal_kadaluarsa', '<=', now()->addDays(30)); + } elseif ($filter === '120') { + // Waspada: expired or <= 4 months remaining + $query->where('tanggal_kadaluarsa', '<=', now()->addMonths(4)); + } + + $obatKadaluarsa = $query->orderBy('tanggal_kadaluarsa', 'asc')->paginate(15); + + return view('kadaluarsa.index', compact('obatKadaluarsa', 'filter')); + } +} diff --git a/app/Http/Controllers/KategoriController.php b/app/Http/Controllers/KategoriController.php new file mode 100644 index 0000000..a8bd58d --- /dev/null +++ b/app/Http/Controllers/KategoriController.php @@ -0,0 +1,55 @@ +paginate(10); + return view('kategori.index', compact('kategoris')); + } + + public function create() + { + return view('kategori.create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'nama' => 'required|string|max:100|unique:kategoris,nama', + 'keterangan' => 'nullable|string|max:255', + ]); + + Kategori::create($validated); + + return redirect()->route('kategori.index')->with('success', 'Kategori berhasil ditambahkan'); + } + + public function edit(Kategori $kategori) + { + return view('kategori.edit', compact('kategori')); + } + + public function update(Request $request, Kategori $kategori) + { + $validated = $request->validate([ + 'nama' => 'required|string|max:100|unique:kategoris,nama,' . $kategori->id, + 'keterangan' => 'nullable|string|max:255', + ]); + + $kategori->update($validated); + + return redirect()->route('kategori.index')->with('success', 'Kategori berhasil diperbarui'); + } + + public function destroy(Kategori $kategori) + { + $kategori->delete(); + return redirect()->route('kategori.index')->with('success', 'Kategori berhasil dihapus'); + } +} diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php new file mode 100644 index 0000000..a7fd94c --- /dev/null +++ b/app/Http/Controllers/LaporanController.php @@ -0,0 +1,101 @@ +get('jenis', 'masuk'); + $tanggalMulai = $request->get('tanggal_mulai', now()->startOfMonth()->format('Y-m-d')); + $tanggalAkhir = $request->get('tanggal_akhir', now()->endOfMonth()->format('Y-m-d')); + + if ($jenis === 'masuk') { + $data = ObatMasuk::with(['kategori', 'satuan', 'supplier']) + ->whereBetween('tanggal_penerimaan', [$tanggalMulai, $tanggalAkhir]) + ->orderBy('tanggal_penerimaan', 'desc') + ->paginate(15); + } else { + $data = ObatKeluar::whereNotIn('status', ['proses', 'dibatalkan']) + ->whereBetween('tanggal_pengeluaran', [$tanggalMulai, $tanggalAkhir]) + ->orderBy('tanggal_pengeluaran', 'desc') + ->paginate(15); + } + + return view('laporan.index', compact('data', 'jenis', 'tanggalMulai', 'tanggalAkhir')); + } + + /** + * Helper to get aggregated (grouped by nama_obat) data for exports. + */ + private function getAggregatedData(string $jenis, string $tanggalMulai, string $tanggalAkhir) + { + if ($jenis === 'masuk') { + return DB::table('obat_masuks') + ->join('satuans', 'obat_masuks.satuan_id', '=', 'satuans.id') + ->whereBetween('tanggal_penerimaan', [$tanggalMulai, $tanggalAkhir]) + ->select( + 'obat_masuks.nama_obat', + 'satuans.nama as satuan', + DB::raw('SUM(obat_masuks.stok) as total_jumlah') + ) + ->groupBy('obat_masuks.nama_obat', 'satuans.nama') + ->orderBy('obat_masuks.nama_obat') + ->get(); + } else { + return DB::table('obat_keluars') + ->join('obat_masuks', 'obat_keluars.obat_masuk_id', '=', 'obat_masuks.id') + ->leftJoin('satuans', 'obat_masuks.satuan_id', '=', 'satuans.id') + ->whereNotIn('obat_keluars.status', ['proses', 'dibatalkan']) + ->whereBetween('obat_keluars.tanggal_pengeluaran', [$tanggalMulai, $tanggalAkhir]) + ->select( + 'obat_keluars.nama_obat', + 'satuans.nama as satuan', + DB::raw('SUM(obat_keluars.jumlah) as total_jumlah') + ) + ->groupBy('obat_keluars.nama_obat', 'satuans.nama') + ->orderBy('obat_keluars.nama_obat') + ->get(); + } + } + + public function exportPdf(Request $request) + { + $jenis = $request->get('jenis', 'masuk'); + $tanggalMulai = $request->get('tanggal_mulai', now()->startOfMonth()->format('Y-m-d')); + $tanggalAkhir = $request->get('tanggal_akhir', now()->endOfMonth()->format('Y-m-d')); + + $title = $jenis === 'masuk' ? 'Laporan Obat Masuk' : 'Laporan Obat Keluar'; + $data = $this->getAggregatedData($jenis, $tanggalMulai, $tanggalAkhir); + + $pdf = Pdf::loadView('laporan.pdf', compact('data', 'jenis', 'title', 'tanggalMulai', 'tanggalAkhir')); + $filename = "laporan-obat-{$jenis}-" . date('Y-m-d') . ".pdf"; + + return $pdf->download($filename); + } + + public function exportExcel(Request $request) + { + $jenis = $request->get('jenis', 'masuk'); + $tanggalMulai = $request->get('tanggal_mulai', now()->startOfMonth()->format('Y-m-d')); + $tanggalAkhir = $request->get('tanggal_akhir', now()->endOfMonth()->format('Y-m-d')); + + $filename = "laporan-obat-{$jenis}-" . date('Y-m-d') . ".xlsx"; + + if ($jenis === 'masuk') { + return Excel::download(new ObatMasukExport($tanggalMulai, $tanggalAkhir), $filename); + } else { + return Excel::download(new ObatKeluarExport($tanggalMulai, $tanggalAkhir), $filename); + } + } +} + diff --git a/app/Http/Controllers/ObatKeluarController.php b/app/Http/Controllers/ObatKeluarController.php new file mode 100644 index 0000000..8355606 --- /dev/null +++ b/app/Http/Controllers/ObatKeluarController.php @@ -0,0 +1,216 @@ +filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('nama_obat', 'like', "%{$search}%") + ->orWhere('kode_batch', 'like', "%{$search}%"); + }); + } + + // Filter by status + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + $obatKeluars = $query->orderBy('tanggal_pengeluaran', 'desc')->paginate(10); + $kategoris = Kategori::all(); + + // Get filter month and year (default to current month) + $filterMonth = $request->get('chart_month', now()->month); + $filterYear = $request->get('chart_year', now()->year); + + // Create date object for the selected month + $selectedDate = Carbon::createFromDate($filterYear, $filterMonth, 1); + + // Pie chart data - Medicine trend (top 5 medicines by usage) for selected month + $medicineTrend = ObatKeluar::selectRaw('nama_obat, SUM(jumlah) as total_jumlah') + ->whereMonth('tanggal_pengeluaran', $filterMonth) + ->whereYear('tanggal_pengeluaran', $filterYear) + ->groupBy('nama_obat') + ->orderByDesc('total_jumlah') + ->limit(5) + ->get(); + + $pieLabels = $medicineTrend->pluck('nama_obat')->toArray(); + $pieData = $medicineTrend->pluck('total_jumlah')->toArray(); + $totalUsage = array_sum($pieData); + + // Generate month options for filter (last 12 months) + $monthOptions = []; + for ($i = 0; $i < 12; $i++) { + $date = now()->subMonths($i); + $monthOptions[] = [ + 'value' => $date->format('Y-m'), + 'label' => $date->translatedFormat('F Y'), + 'month' => $date->month, + 'year' => $date->year, + ]; + } + + return view('obat-keluar.index', compact( + 'obatKeluars', + 'kategoris', + 'pieLabels', + 'pieData', + 'totalUsage', + 'monthOptions', + 'filterMonth', + 'filterYear', + 'selectedDate' + )); + } + + public function create() + { + $kategoris = Kategori::all(); + // Ambil obat dari tabel obat_masuks yang masih ada stoknya + $obats = ObatMasuk::where('stok', '>', 0) + ->where('tanggal_kadaluarsa', '>=', now()) + ->with('kategori') + ->orderBy('nama_obat') + ->get(); + + return view('obat-keluar.create', compact('kategoris', 'obats')); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'obat_masuk_id' => 'required|exists:obat_masuks,id', + 'kode_batch' => 'required|string|max:50', + 'barcode' => 'nullable|string|max:100', + 'sumber_dana' => 'nullable|string|max:200', + 'jumlah' => 'required|integer|min:1', + 'harga' => 'nullable|numeric|min:0', + 'harga_total' => 'nullable|numeric|min:0', + 'tujuan_pemakaian' => 'required|string|max:200', + 'tanggal_kadaluarsa' => 'required|date', + 'tanggal_pengeluaran' => 'required|date', + 'no_pengeluaran' => 'nullable|string|max:50', + 'nama_petugas' => 'required|string|max:100', + 'nama_penerima' => 'required|string|max:100', + 'catatan' => 'nullable|string', + 'status' => 'nullable|in:proses,selesai,dibatalkan', + ]); + + // Cek stok tersedia + $obatMasuk = ObatMasuk::findOrFail($validated['obat_masuk_id']); + + if ($obatMasuk->stok < $validated['jumlah']) { + return back()->withErrors(['jumlah' => 'Jumlah melebihi stok yang tersedia (' . $obatMasuk->stok . ')'])->withInput(); + } + + DB::transaction(function () use ($validated, $obatMasuk) { + // Simpan nama obat dari obat masuk + $validated['nama_obat'] = $obatMasuk->nama_obat; + $validated['user_id'] = auth()->id(); + $validated['status'] = $validated['status'] ?? 'proses'; + + // Buat obat keluar + ObatKeluar::create($validated); + + // Kurangi stok obat masuk + $obatMasuk->decrement('stok', $validated['jumlah']); + }); + + return redirect()->route('obat-keluar.index')->with('success', 'Data obat keluar berhasil ditambahkan dan stok obat masuk telah dikurangi'); + } + + public function show(ObatKeluar $obatKeluar) + { + $obatKeluar->load(['obatMasuk.kategori', 'user']); + return view('obat-keluar.show', compact('obatKeluar')); + } + + public function edit(ObatKeluar $obatKeluar) + { + $kategoris = Kategori::all(); + $obats = ObatMasuk::where(function ($q) { + $q->where('stok', '>', 0) + ->where('tanggal_kadaluarsa', '>=', now()); + }) + ->orWhere('id', $obatKeluar->obat_masuk_id) + ->with('kategori') + ->orderBy('nama_obat') + ->get(); + + return view('obat-keluar.edit', compact('obatKeluar', 'kategoris', 'obats')); + } + + public function update(Request $request, ObatKeluar $obatKeluar) + { + $validated = $request->validate([ + 'obat_masuk_id' => 'required|exists:obat_masuks,id', + 'kode_batch' => 'required|string|max:50', + 'barcode' => 'nullable|string|max:100', + 'sumber_dana' => 'nullable|string|max:200', + 'jumlah' => 'required|integer|min:1', + 'harga' => 'nullable|numeric|min:0', + 'harga_total' => 'nullable|numeric|min:0', + 'tujuan_pemakaian' => 'required|string|max:200', + 'tanggal_kadaluarsa' => 'required|date', + 'tanggal_pengeluaran' => 'required|date', + 'no_pengeluaran' => 'nullable|string|max:50', + 'nama_petugas' => 'required|string|max:100', + 'nama_penerima' => 'required|string|max:100', + 'catatan' => 'nullable|string', + 'status' => 'nullable|in:proses,selesai,dibatalkan', + ]); + + $obatMasuk = ObatMasuk::findOrFail($validated['obat_masuk_id']); + + // Hitung selisih jumlah + $selisih = $validated['jumlah'] - $obatKeluar->jumlah; + + if ($selisih > 0 && $obatMasuk->stok < $selisih) { + return back()->withErrors(['jumlah' => 'Jumlah melebihi stok yang tersedia'])->withInput(); + } + + DB::transaction(function () use ($validated, $obatKeluar, $obatMasuk, $selisih) { + // Update nama obat + $validated['nama_obat'] = $obatMasuk->nama_obat; + + // Update stok + if ($selisih > 0) { + $obatMasuk->decrement('stok', $selisih); + } elseif ($selisih < 0) { + $obatMasuk->increment('stok', abs($selisih)); + } + + $obatKeluar->update($validated); + }); + + return redirect()->route('obat-keluar.index')->with('success', 'Data obat keluar berhasil diperbarui'); + } + + public function destroy(ObatKeluar $obatKeluar) + { + DB::transaction(function () use ($obatKeluar) { + // Kembalikan stok ke obat masuk + if ($obatKeluar->obatMasuk) { + $obatKeluar->obatMasuk->increment('stok', $obatKeluar->jumlah); + } + + $obatKeluar->delete(); + }); + + return redirect()->route('obat-keluar.index')->with('success', 'Data obat keluar berhasil dihapus dan stok dikembalikan'); + } +} diff --git a/app/Http/Controllers/ObatMasukController.php b/app/Http/Controllers/ObatMasukController.php new file mode 100644 index 0000000..8d8446d --- /dev/null +++ b/app/Http/Controllers/ObatMasukController.php @@ -0,0 +1,166 @@ +filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('nama_obat', 'like', "%{$search}%") + ->orWhere('kode_batch', 'like', "%{$search}%"); + }); + } + + // Filter by category + if ($request->filled('kategori')) { + $query->where('kategori_id', $request->kategori); + } + + // Filter by status + if ($request->filled('status')) { + if ($request->status === 'tersedia') { + $query->where('stok', '>', 0); + } else if ($request->status === 'habis') { + $query->where('stok', 0); + } + } + + $obatMasuks = $query->with(['kategori', 'satuan', 'supplier']) + ->orderBy('tanggal_penerimaan', 'desc') + ->paginate(10); + $kategoris = Kategori::all(); + + // Get filter month and year (default to current month) + $filterMonth = $request->get('chart_month', now()->month); + $filterYear = $request->get('chart_year', now()->year); + + // Create date object for the selected month + $selectedDate = Carbon::createFromDate($filterYear, $filterMonth, 1); + + // Pie chart data - Medicine trend (top 5 medicines by stock received) for selected month + $medicineTrend = ObatMasuk::selectRaw('nama_obat, SUM(stok) as total_stok') + ->whereMonth('tanggal_penerimaan', $filterMonth) + ->whereYear('tanggal_penerimaan', $filterYear) + ->groupBy('nama_obat') + ->orderByDesc('total_stok') + ->limit(5) + ->get(); + + $pieLabels = $medicineTrend->pluck('nama_obat')->toArray(); + $pieData = $medicineTrend->pluck('total_stok')->toArray(); + $totalStock = array_sum($pieData); + + // Generate month options for filter (last 12 months) + $monthOptions = []; + for ($i = 0; $i < 12; $i++) { + $date = now()->subMonths($i); + $monthOptions[] = [ + 'value' => $date->format('Y-m'), + 'label' => $date->translatedFormat('F Y'), + 'month' => $date->month, + 'year' => $date->year, + ]; + } + + return view('obat-masuk.index', compact( + 'obatMasuks', + 'kategoris', + 'pieLabels', + 'pieData', + 'totalStock', + 'monthOptions', + 'filterMonth', + 'filterYear', + 'selectedDate' + )); + } + + public function create() + { + $kategoris = Kategori::orderBy('nama')->get(); + $satuans = Satuan::orderBy('nama')->get(); + $suppliers = Supplier::orderBy('nama')->get(); + + return view('obat-masuk.create', compact('kategoris', 'satuans', 'suppliers')); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'nama_obat' => 'required|string|max:200', + 'kategori_id' => 'required|exists:kategoris,id', + 'satuan_id' => 'required|exists:satuans,id', + 'sumber_dana' => 'nullable|string|max:200', + 'kode_batch' => 'required|string|max:50', + 'barcode' => 'nullable|string|max:100', + 'stok' => 'required|integer|min:1', + 'tanggal_penerimaan' => 'required|date', + 'tanggal_kadaluarsa' => 'required|date|after:tanggal_penerimaan', + 'no_faktur' => 'nullable|string|max:50', + 'no_sbbk' => 'nullable|string|max:100', + 'catatan' => 'nullable|string', + ]); + + $validated['user_id'] = auth()->id(); + + ObatMasuk::create($validated); + + return redirect()->route('obat-masuk.index')->with('success', 'Data obat masuk berhasil ditambahkan'); + } + + public function show(ObatMasuk $obatMasuk) + { + $obatMasuk->load(['kategori', 'satuan', 'supplier', 'user']); + return view('obat-masuk.show', compact('obatMasuk')); + } + + public function edit(ObatMasuk $obatMasuk) + { + $kategoris = Kategori::orderBy('nama')->get(); + $satuans = Satuan::orderBy('nama')->get(); + $suppliers = Supplier::orderBy('nama')->get(); + + return view('obat-masuk.edit', compact('obatMasuk', 'kategoris', 'satuans', 'suppliers')); + } + + public function update(Request $request, ObatMasuk $obatMasuk) + { + $validated = $request->validate([ + 'nama_obat' => 'required|string|max:200', + 'kategori_id' => 'required|exists:kategoris,id', + 'satuan_id' => 'required|exists:satuans,id', + 'sumber_dana' => 'nullable|string|max:200', + 'kode_batch' => 'required|string|max:50', + 'barcode' => 'nullable|string|max:100', + 'stok' => 'required|integer|min:0', + 'tanggal_penerimaan' => 'required|date', + 'tanggal_kadaluarsa' => 'required|date|after:tanggal_penerimaan', + 'no_faktur' => 'nullable|string|max:50', + 'no_sbbk' => 'nullable|string|max:100', + 'catatan' => 'nullable|string', + ]); + + $obatMasuk->update($validated); + + return redirect()->route('obat-masuk.index')->with('success', 'Data obat masuk berhasil diperbarui'); + } + + public function destroy(ObatMasuk $obatMasuk) + { + $obatMasuk->delete(); + return redirect()->route('obat-masuk.index')->with('success', 'Data obat masuk berhasil dihapus'); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100644 index 0000000..c314592 --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,97 @@ + $request->user(), + ]); + } + + /** + * Update the user's profile information. + */ + public function update(Request $request): RedirectResponse + { + $user = $request->user(); + + // Handle photo-only upload form + if ($request->input('_photo_only')) { + $request->validate([ + 'profile_photo' => ['required', 'image', 'max:2048'], + ]); + + if ($user->profile_photo) { + Storage::disk('public')->delete($user->profile_photo); + } + + $user->profile_photo = $request->file('profile_photo')->store('profile-photos', 'public'); + $user->save(); + + return Redirect::route('profile.edit')->with('status', 'profile-updated'); + } + + // Handle full profile update + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'nip' => ['nullable', 'string', 'max:50'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', + \Illuminate\Validation\Rule::unique('users')->ignore($user->id)], + 'phone' => ['nullable', 'string', 'max:20'], + 'gender' => ['required', 'in:laki-laki,perempuan'], + 'address' => ['nullable', 'string', 'max:1000'], + 'division' => ['nullable', 'string', 'max:255'], + 'position' => ['nullable', 'string', 'max:255'], + ]); + + $user->fill($validated); + + if ($user->isDirty('email')) { + $user->email_verified_at = null; + } + + $user->save(); + + return Redirect::route('profile.edit')->with('status', 'profile-updated'); + } + + + /** + * Delete the user's account. + */ + public function destroy(Request $request): RedirectResponse + { + $request->validateWithBag('userDeletion', [ + 'password' => ['required', 'current_password'], + ]); + + $user = $request->user(); + + if ($user->profile_photo) { + Storage::disk('public')->delete($user->profile_photo); + } + + Auth::logout(); + + $user->delete(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return Redirect::to('/'); + } +} diff --git a/app/Http/Controllers/ResepController.php b/app/Http/Controllers/ResepController.php new file mode 100644 index 0000000..d57ac3e --- /dev/null +++ b/app/Http/Controllers/ResepController.php @@ -0,0 +1,382 @@ +filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('no_resep', 'like', "%{$search}%") + ->orWhere('nama_pasien', 'like', "%{$search}%") + ->orWhere('no_rm', 'like', "%{$search}%"); + }); + } + + // Filter by status + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + $reseps = $query->orderBy('tanggal_resep', 'desc')->paginate(10); + + return view('resep.index', compact('reseps')); + } + + /** + * Show the form for creating a new prescription. + */ + public function create() + { + // Get medicines with available stock + $obats = ObatMasuk::where('stok', '>', 0) + ->where('tanggal_kadaluarsa', '>=', now()) + ->with('kategori', 'satuan') + ->orderBy('nama_obat') + ->get(); + + return view('resep.create', compact('obats')); + } + + /** + * Store a newly created prescription in storage. + */ + public function store(Request $request) + { + $validated = $request->validate([ + // Data Resep + 'nama_dokter' => 'required|string|max:100', + 'no_sip' => 'nullable|string|max:50', + 'tanggal_resep' => 'required|date', + 'jenis_penjamin' => 'required|in:umum,BPJS Kes,BPJS Naker,Jamkesmas/KIS,R. Inap', + 'jenis_layanan' => 'required|in:BP,KIA,Gigi,UGD,Lainnya', + // Data Pasien + 'no_rm' => 'nullable|string|max:50', + 'nama_pasien' => 'required|string|max:100', + 'alamat_pasien' => 'nullable|string|max:255', + 'jenis_kelamin' => 'required|in:L,P', + 'umur_pasien' => 'required|integer|min:0|max:150', + 'berat_badan' => 'required|numeric|min:0|max:500', + 'diagnosa' => 'required|string', + 'catatan' => 'nullable|string', + // Daftar Obat + 'items' => 'required|array|min:1', + 'items.*.obat_masuk_id' => 'required|exists:obat_masuks,id', + 'items.*.jumlah' => 'required|integer|min:1', + 'items.*.aturan_pakai' => 'nullable|string|max:255', + ]); + + // Validate stock availability + foreach ($validated['items'] as $item) { + $obatMasuk = ObatMasuk::find($item['obat_masuk_id']); + if ($obatMasuk->stok < $item['jumlah']) { + return back()->withErrors([ + 'items' => "Stok obat {$obatMasuk->nama_obat} tidak mencukupi. Tersedia: {$obatMasuk->stok}" + ])->withInput(); + } + } + + DB::transaction(function () use ($validated, $request) { + // Create resep with status 'proses' (not 'selesai') + $resep = Resep::create([ + 'no_resep' => Resep::generateNoResep(), + 'user_id' => auth()->id(), + 'nama_dokter' => $validated['nama_dokter'], + 'no_sip' => $validated['no_sip'] ?? null, + 'jenis_penjamin' => $validated['jenis_penjamin'], + 'jenis_layanan' => $validated['jenis_layanan'], + 'no_rm' => $validated['no_rm'] ?? null, + 'nama_pasien' => $validated['nama_pasien'], + 'alamat_pasien' => $validated['alamat_pasien'] ?? null, + 'jenis_kelamin' => $validated['jenis_kelamin'], + 'umur_pasien' => $validated['umur_pasien'], + 'berat_badan' => $validated['berat_badan'], + 'tanggal_resep' => $validated['tanggal_resep'], + 'diagnosa' => $validated['diagnosa'], + 'catatan' => $validated['catatan'] ?? null, + 'status' => 'proses', // Changed from 'selesai' to 'proses' + 'is_read' => false, + ]); + + // Create resep items and reduce stock + foreach ($validated['items'] as $item) { + $obatMasuk = ObatMasuk::find($item['obat_masuk_id']); + + // Create resep item + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $item['obat_masuk_id'], + 'nama_obat' => $obatMasuk->nama_obat, + 'jumlah' => $item['jumlah'], + 'aturan_pakai' => $item['aturan_pakai'] ?? null, + ]); + + // Create obat keluar record with status 'proses' + ObatKeluar::create([ + 'obat_masuk_id' => $item['obat_masuk_id'], + 'nama_obat' => $obatMasuk->nama_obat, + 'sumber_dana' => $obatMasuk->sumber_dana, + 'user_id' => auth()->id(), + 'kode_batch' => $obatMasuk->kode_batch, + 'barcode' => $obatMasuk->barcode, + 'jumlah' => $item['jumlah'], + 'tujuan_pemakaian' => 'Resep: ' . $resep->no_resep . ' - ' . $validated['nama_pasien'], + 'tanggal_pengeluaran' => $validated['tanggal_resep'], + 'tanggal_kadaluarsa' => $obatMasuk->tanggal_kadaluarsa, + 'no_pengeluaran' => $resep->no_resep, + 'nama_petugas' => auth()->user()->name, + 'nama_penerima' => $validated['nama_pasien'], + 'catatan' => 'Otomatis dari resep ' . $resep->no_resep, + 'status' => 'proses', // Changed from 'terkirim' to 'proses' + ]); + + // Do NOT reduce stock here because status is 'proses' + } + }); + + return redirect()->route('resep.index')->with('success', 'Resep berhasil dibuat.'); + } + + /** + * Display the specified prescription. + */ + public function show(Resep $resep) + { + $resep->load(['user', 'items.obatMasuk.satuan']); + + // Mark as read if apoteker is viewing + if (auth()->user()->isApoteker()) { + $resep->markAsRead(); + } + + return view('resep.show', compact('resep')); + } + + /** + * Show the form for editing the specified prescription. + */ + public function edit(Resep $resep) + { + // Resep yang sudah selesai tidak bisa diedit + if ($resep->status === 'selesai') { + return redirect()->route('resep.show', $resep) + ->with('error', 'Resep dengan status selesai tidak dapat diedit.'); + } + + $resep->load(['items']); + + $obats = ObatMasuk::where(function ($q) { + $q->where('stok', '>', 0) + ->where('tanggal_kadaluarsa', '>=', now()); + }) + ->orWhereIn('id', $resep->items->pluck('obat_masuk_id')) + ->with('kategori', 'satuan') + ->orderBy('nama_obat') + ->get(); + + return view('resep.edit', compact('resep', 'obats')); + } + + /** + * Update the specified prescription in storage. + */ + public function update(Request $request, Resep $resep) + { + // Resep yang sudah selesai tidak bisa diubah + if ($resep->status === 'selesai') { + return redirect()->route('resep.show', $resep) + ->with('error', 'Resep dengan status selesai tidak dapat diedit.'); + } + + $validated = $request->validate([ + // Data Resep + 'nama_dokter' => 'required|string|max:100', + 'no_sip' => 'nullable|string|max:50', + 'tanggal_resep' => 'required|date', + 'jenis_penjamin' => 'required|in:umum,BPJS Kes,BPJS Naker,Jamkesmas/KIS,R. Inap', + 'jenis_layanan' => 'required|in:BP,KIA,Gigi,UGD,Lainnya', + // Data Pasien + 'no_rm' => 'nullable|string|max:50', + 'nama_pasien' => 'required|string|max:100', + 'alamat_pasien' => 'nullable|string|max:255', + 'jenis_kelamin' => 'required|in:L,P', + 'umur_pasien' => 'required|integer|min:0|max:150', + 'berat_badan' => 'required|numeric|min:0|max:500', + 'diagnosa' => 'required|string', + 'catatan' => 'nullable|string', + 'status' => 'required|in:proses,selesai,dibatalkan', + // Daftar Obat + 'items' => 'required|array|min:1', + 'items.*.obat_masuk_id' => 'required|exists:obat_masuks,id', + 'items.*.jumlah' => 'required|integer|min:1', + 'items.*.aturan_pakai' => 'nullable|string|max:255', + ]); + + DB::transaction(function () use ($validated, $resep) { + // Restore old stock first (only if status was selesai, but update is not allowed for selesai) + foreach ($resep->items as $oldItem) { + // Delete related obat keluar + ObatKeluar::where('no_pengeluaran', $resep->no_resep) + ->where('obat_masuk_id', $oldItem->obat_masuk_id) + ->delete(); + } + + // Delete old items + $resep->items()->delete(); + + // Validate new stock + foreach ($validated['items'] as $item) { + $obatMasuk = ObatMasuk::find($item['obat_masuk_id']); + if ($obatMasuk->stok < $item['jumlah']) { + throw new \Exception("Stok obat {$obatMasuk->nama_obat} tidak mencukupi. Tersedia: {$obatMasuk->stok}"); + } + } + + // Update resep + $resep->update([ + 'nama_dokter' => $validated['nama_dokter'], + 'no_sip' => $validated['no_sip'] ?? null, + 'jenis_penjamin' => $validated['jenis_penjamin'], + 'jenis_layanan' => $validated['jenis_layanan'], + 'no_rm' => $validated['no_rm'] ?? null, + 'nama_pasien' => $validated['nama_pasien'], + 'alamat_pasien' => $validated['alamat_pasien'] ?? null, + 'jenis_kelamin' => $validated['jenis_kelamin'], + 'umur_pasien' => $validated['umur_pasien'], + 'berat_badan' => $validated['berat_badan'], + 'tanggal_resep' => $validated['tanggal_resep'], + 'diagnosa' => $validated['diagnosa'], + 'catatan' => $validated['catatan'] ?? null, + 'status' => $validated['status'], + ]); + + // Create new items and reduce stock + foreach ($validated['items'] as $item) { + $obatMasuk = ObatMasuk::find($item['obat_masuk_id']); + + // Create resep item + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $item['obat_masuk_id'], + 'nama_obat' => $obatMasuk->nama_obat, + 'jumlah' => $item['jumlah'], + 'aturan_pakai' => $item['aturan_pakai'] ?? null, + ]); + + // Create obat keluar record + ObatKeluar::create([ + 'obat_masuk_id' => $item['obat_masuk_id'], + 'nama_obat' => $obatMasuk->nama_obat, + 'sumber_dana' => $obatMasuk->sumber_dana, + 'user_id' => auth()->id(), + 'kode_batch' => $obatMasuk->kode_batch, + 'barcode' => $obatMasuk->barcode, + 'jumlah' => $item['jumlah'], + 'tujuan_pemakaian' => 'Resep: ' . $resep->no_resep . ' - ' . $validated['nama_pasien'], + 'tanggal_pengeluaran' => $validated['tanggal_resep'], + 'tanggal_kadaluarsa' => $obatMasuk->tanggal_kadaluarsa, + 'no_pengeluaran' => $resep->no_resep, + 'nama_petugas' => auth()->user()->name, + 'nama_penerima' => $validated['nama_pasien'], + 'catatan' => 'Otomatis dari resep ' . $resep->no_resep, + 'status' => $validated['status'] === 'selesai' ? 'selesai' : ($validated['status'] === 'dibatalkan' ? 'dibatalkan' : 'proses'), + ]); + + // Reduce stock only if status is selesai + if ($validated['status'] === 'selesai') { + $obatMasuk->decrement('stok', $item['jumlah']); + } + } + }); + + return redirect()->route('resep.index')->with('success', 'Resep berhasil diperbarui.'); + } + + /** + * Update the status of the specified prescription (by apoteker). + */ + public function updateStatus(Request $request, Resep $resep) + { + // Resep yang sudah selesai tidak bisa diubah statusnya + if ($resep->status === 'selesai') { + return redirect()->route('resep.index') + ->with('error', 'Status resep yang sudah selesai tidak dapat diubah.'); + } + + $validated = $request->validate([ + 'status' => 'required|in:proses,selesai,dibatalkan', + ]); + + + DB::transaction(function () use ($validated, $resep) { + $oldStatus = $resep->status; + + $resep->update(['status' => $validated['status']]); + + // Also update related obat keluar records + ObatKeluar::where('no_pengeluaran', $resep->no_resep) + ->update(['status' => $validated['status']]); + + // If changing to 'selesai', reduce stock + if ($oldStatus !== 'selesai' && $validated['status'] === 'selesai') { + foreach ($resep->items as $item) { + if ($item->obatMasuk) { + $item->obatMasuk->decrement('stok', $item->jumlah); + } + } + } + }); + + return redirect()->route('resep.index')->with('success', 'Status resep berhasil diperbarui.'); + } + + /** + * Remove the specified prescription from storage. + */ + public function destroy(Resep $resep) + { + DB::transaction(function () use ($resep) { + // Restore stock for each item ONLY IF status was selesai + if ($resep->status === 'selesai') { + foreach ($resep->items as $item) { + if ($item->obatMasuk) { + $item->obatMasuk->increment('stok', $item->jumlah); + } + } + } + + // Delete related obat keluar + ObatKeluar::where('no_pengeluaran', $resep->no_resep)->delete(); + + // Delete resep (items will cascade) + $resep->delete(); + }); + + return redirect()->route('resep.index')->with('success', 'Resep berhasil dihapus dan stok dikembalikan.'); + } + + /** + * Print the prescription. + */ + public function print(Resep $resep) + { + $resep->load(['user', 'items.obatMasuk.satuan']); + return view('resep.print', compact('resep')); + } +} + diff --git a/app/Http/Controllers/SatuanController.php b/app/Http/Controllers/SatuanController.php new file mode 100644 index 0000000..baaa17f --- /dev/null +++ b/app/Http/Controllers/SatuanController.php @@ -0,0 +1,55 @@ +paginate(10); + return view('satuan.index', compact('satuans')); + } + + public function create() + { + return view('satuan.create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'nama' => 'required|string|max:100|unique:satuans,nama', + 'keterangan' => 'nullable|string|max:255', + ]); + + Satuan::create($validated); + + return redirect()->route('satuan.index')->with('success', 'Satuan berhasil ditambahkan'); + } + + public function edit(Satuan $satuan) + { + return view('satuan.edit', compact('satuan')); + } + + public function update(Request $request, Satuan $satuan) + { + $validated = $request->validate([ + 'nama' => 'required|string|max:100|unique:satuans,nama,' . $satuan->id, + 'keterangan' => 'nullable|string|max:255', + ]); + + $satuan->update($validated); + + return redirect()->route('satuan.index')->with('success', 'Satuan berhasil diperbarui'); + } + + public function destroy(Satuan $satuan) + { + $satuan->delete(); + return redirect()->route('satuan.index')->with('success', 'Satuan berhasil dihapus'); + } +} diff --git a/app/Http/Controllers/UserManagementController.php b/app/Http/Controllers/UserManagementController.php new file mode 100644 index 0000000..4c03ccf --- /dev/null +++ b/app/Http/Controllers/UserManagementController.php @@ -0,0 +1,127 @@ +filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('nip', 'like', "%{$search}%"); + }); + } + + if ($request->filled('role')) { + $query->where('role', $request->role); + } + + $users = $query->orderBy('name')->paginate(10); + + return view('user-management.index', compact('users')); + } + + /** + * Show the form for creating a new user. + */ + public function create() + { + return view('user-management.create'); + } + + /** + * Store a newly created user in storage. + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users', + 'nip' => 'nullable|string|max:50', + 'phone' => 'nullable|string|max:20', + 'gender' => 'nullable|in:L,P', + 'role' => 'required|in:dokter,apoteker', + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + $validated['password'] = Hash::make($validated['password']); + + User::create($validated); + + return redirect()->route('user-management.index')->with('success', 'Akun berhasil dibuat.'); + } + + /** + * Show the form for editing the specified user. + */ + public function edit(User $user) + { + if ($user->role === 'superadmin') { + abort(403, 'Tidak dapat mengedit akun superadmin.'); + } + + return view('user-management.edit', compact('user')); + } + + /** + * Update the specified user in storage. + */ + public function update(Request $request, User $user) + { + if ($user->role === 'superadmin') { + abort(403, 'Tidak dapat mengedit akun superadmin.'); + } + + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, + 'nip' => 'nullable|string|max:50', + 'phone' => 'nullable|string|max:20', + 'gender' => 'nullable|in:L,P', + 'role' => 'required|in:dokter,apoteker', + 'password' => ['nullable', 'confirmed', Rules\Password::defaults()], + ]); + + if (!empty($validated['password'])) { + $validated['password'] = Hash::make($validated['password']); + } else { + unset($validated['password']); + } + + $user->update($validated); + + return redirect()->route('user-management.index')->with('success', 'Akun berhasil diperbarui.'); + } + + /** + * Remove the specified user from storage. + */ + public function destroy(User $user) + { + if ($user->id === auth()->id()) { + return redirect()->route('user-management.index') + ->with('error', 'Tidak dapat menghapus akun sendiri.'); + } + + if ($user->role === 'superadmin') { + abort(403, 'Tidak dapat menghapus akun superadmin.'); + } + + $user->delete(); + + return redirect()->route('user-management.index')->with('success', 'Akun berhasil dihapus.'); + } +} diff --git a/app/Http/Middleware/CheckRole.php b/app/Http/Middleware/CheckRole.php new file mode 100644 index 0000000..ccca71e --- /dev/null +++ b/app/Http/Middleware/CheckRole.php @@ -0,0 +1,37 @@ +user()) { + return redirect()->route('login'); + } + + $userRole = $request->user()->role; + + // Handle comma-separated roles (e.g., 'dokter,apoteker') + $allowedRoles = []; + foreach ($roles as $role) { + $allowedRoles = array_merge($allowedRoles, explode(',', $role)); + } + + if (!in_array($userRole, $allowedRoles)) { + abort(403, 'Anda tidak memiliki akses ke halaman ini.'); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 0000000..2574642 --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,85 @@ +|string> + */ + public function rules(): array + { + return [ + 'email' => ['required', 'string', 'email'], + 'password' => ['required', 'string'], + ]; + } + + /** + * Attempt to authenticate the request's credentials. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function authenticate(): void + { + $this->ensureIsNotRateLimited(); + + if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { + RateLimiter::hit($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => trans('auth.failed'), + ]); + } + + RateLimiter::clear($this->throttleKey()); + } + + /** + * Ensure the login request is not rate limited. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function ensureIsNotRateLimited(): void + { + if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { + return; + } + + event(new Lockout($this)); + + $seconds = RateLimiter::availableIn($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ]), + ]); + } + + /** + * Get the rate limiting throttle key for the request. + */ + public function throttleKey(): string + { + return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip()); + } +} diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php new file mode 100644 index 0000000..06a415e --- /dev/null +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -0,0 +1,37 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'max:255'], + 'nip' => ['required', 'string', 'max:50'], + 'email' => [ + 'required', + 'string', + 'lowercase', + 'email', + 'max:255', + Rule::unique(User::class)->ignore($this->user()->id), + ], + 'phone' => ['required', 'string', 'max:20'], + 'gender' => ['required', 'in:laki-laki,perempuan'], + 'address' => ['nullable', 'string', 'max:1000'], + 'division' => ['required', 'string', 'max:255'], + 'position' => ['nullable', 'string', 'max:255'], + 'profile_photo' => ['nullable', 'image', 'max:2048'], + ]; + } +} diff --git a/app/Models/Kategori.php b/app/Models/Kategori.php new file mode 100644 index 0000000..b965d81 --- /dev/null +++ b/app/Models/Kategori.php @@ -0,0 +1,16 @@ +hasMany(Obat::class); + } +} diff --git a/app/Models/Obat.php b/app/Models/Obat.php new file mode 100644 index 0000000..b96a15b --- /dev/null +++ b/app/Models/Obat.php @@ -0,0 +1,34 @@ +belongsTo(Kategori::class); + } + + public function obatMasuks(): HasMany + { + return $this->hasMany(ObatMasuk::class); + } + + public function obatKeluars(): HasMany + { + return $this->hasMany(ObatKeluar::class); + } + + public function getTotalStokAttribute(): int + { + $masuk = $this->obatMasuks()->sum('stok'); + $keluar = $this->obatKeluars()->where('status', 'terkirim')->sum('jumlah'); + return $masuk - $keluar; + } +} diff --git a/app/Models/ObatKeluar.php b/app/Models/ObatKeluar.php new file mode 100644 index 0000000..9922bc6 --- /dev/null +++ b/app/Models/ObatKeluar.php @@ -0,0 +1,52 @@ + 'date', + 'tanggal_kadaluarsa' => 'date', + 'harga' => 'decimal:2', + 'harga_total' => 'decimal:2', + ]; + + public function obatMasuk(): BelongsTo + { + return $this->belongsTo(ObatMasuk::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function getSisaHariAttribute(): int + { + return Carbon::now()->diffInDays($this->tanggal_kadaluarsa, false); + } +} diff --git a/app/Models/ObatMasuk.php b/app/Models/ObatMasuk.php new file mode 100644 index 0000000..98d7a48 --- /dev/null +++ b/app/Models/ObatMasuk.php @@ -0,0 +1,83 @@ + 'date', + 'tanggal_kadaluarsa' => 'date', + 'harga_beli' => 'decimal:2', + 'harga_jual' => 'decimal:2', + ]; + + public function obat(): BelongsTo + { + return $this->belongsTo(Obat::class); + } + + public function kategori(): BelongsTo + { + return $this->belongsTo(Kategori::class); + } + + public function satuan(): BelongsTo + { + return $this->belongsTo(Satuan::class); + } + + public function supplier(): BelongsTo + { + return $this->belongsTo(Supplier::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function getSisaHariAttribute(): int + { + return (int) Carbon::now()->diffInDays($this->tanggal_kadaluarsa, false); + } + + public function getStatusKadaluarsaAttribute(): string + { + $sisaHari = $this->sisa_hari; + if ($sisaHari <= 30) { + return 'kritis'; + } elseif ($sisaHari <= 60) { + return 'waspada'; + } else { + return 'aman'; + } + } + + public function getStatusStokAttribute(): string + { + return $this->stok > 0 ? 'tersedia' : 'habis'; + } +} diff --git a/app/Models/Resep.php b/app/Models/Resep.php new file mode 100644 index 0000000..5cb074e --- /dev/null +++ b/app/Models/Resep.php @@ -0,0 +1,113 @@ + 'date', + 'berat_badan' => 'decimal:2', + 'is_read' => 'boolean', + ]; + + /** + * Relasi ke User (Dokter yang membuat resep) + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Alias untuk dokter + */ + public function dokter(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } + + /** + * Relasi ke item-item resep + */ + public function items(): HasMany + { + return $this->hasMany(ResepItem::class); + } + + /** + * Generate nomor resep otomatis + */ + public static function generateNoResep(): string + { + $today = now()->format('Ymd'); + $lastResep = self::whereDate('created_at', today()) + ->orderBy('id', 'desc') + ->first(); + + if ($lastResep) { + $lastNumber = (int) substr($lastResep->no_resep, -4); + $newNumber = $lastNumber + 1; + } else { + $newNumber = 1; + } + + return 'RSP-' . $today . '-' . str_pad($newNumber, 4, '0', STR_PAD_LEFT); + } + + /** + * Get status badge color + */ + public function getStatusColorAttribute(): string + { + return match($this->status) { + 'selesai' => 'terkirim', + 'proses' => 'proses', + 'dibatalkan' => 'dibatalkan', + default => 'proses', + }; + } + + /** + * Scope for unread prescriptions + */ + public function scopeUnread(Builder $query): Builder + { + return $query->where('is_read', false); + } + + /** + * Mark prescription as read + */ + public function markAsRead(): void + { + if (!$this->is_read) { + $this->update(['is_read' => true]); + } + } +} + diff --git a/app/Models/ResepItem.php b/app/Models/ResepItem.php new file mode 100644 index 0000000..14d07ae --- /dev/null +++ b/app/Models/ResepItem.php @@ -0,0 +1,33 @@ +belongsTo(Resep::class); + } + + /** + * Relasi ke ObatMasuk + */ + public function obatMasuk(): BelongsTo + { + return $this->belongsTo(ObatMasuk::class); + } +} diff --git a/app/Models/Satuan.php b/app/Models/Satuan.php new file mode 100644 index 0000000..3b78896 --- /dev/null +++ b/app/Models/Satuan.php @@ -0,0 +1,10 @@ +hasMany(ObatMasuk::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..92731e3 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,97 @@ + */ + use HasFactory, Notifiable; + + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = [ + 'name', + 'nip', + 'email', + 'phone', + 'gender', + 'address', + 'division', + 'position', + 'profile_photo', + 'password', + 'role', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } + + /** + * Check if user is a superadmin + */ + public function isSuperAdmin(): bool + { + return $this->role === 'superadmin'; + } + + /** + * Check if user is a dokter + */ + public function isDokter(): bool + { + return $this->role === 'dokter'; + } + + /** + * Check if user is an apoteker + */ + public function isApoteker(): bool + { + return $this->role === 'apoteker'; + } + + /** + * Check if user can manage (create/edit/delete) resep + */ + public function canManageResep(): bool + { + return $this->isDokter(); + } + + /** + * Get user's reseps (prescriptions they created) + */ + public function reseps(): HasMany + { + return $this->hasMany(Resep::class); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..79623fc --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,54 @@ +subject('Pemberitahuan Reset Password - MedData') + ->greeting('Halo ' . $notifiable->name . ',') + ->line('Anda menerima email ini karena kami menerima permintaan reset password untuk akun Anda.') + ->action('Reset Password Sekarang', url(route('password.reset', [ + 'token' => $token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false))) + ->line('Link reset password ini akan kadaluarsa dalam ' . config('auth.passwords.'.config('auth.defaults.passwords').'.expire') . ' menit.') + ->line('Jika Anda tidak melakukan permintaan reset password, abaikan email ini.') + ->salutation('Salam hormat, Tim MedData Puskesmas'); + }); + + // Share unread resep count with sidebar for notification badge + View::composer('components.sidebar', function ($view) { + $unreadResepCount = 0; + + if (auth()->check() && auth()->user()->isApoteker()) { + // Show unread resep count only for apoteker + $unreadResepCount = Resep::unread()->count(); + } + + $view->with('unreadResepCount', $unreadResepCount); + }); + } +} + diff --git a/app/View/Components/AppLayout.php b/app/View/Components/AppLayout.php new file mode 100644 index 0000000..de0d46f --- /dev/null +++ b/app/View/Components/AppLayout.php @@ -0,0 +1,17 @@ +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..82d42e7 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,20 @@ + withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->alias([ + 'role' => \App\Http\Middleware\CheckRole::class, + ]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + // + })->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 0000000..38b258d --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,5 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.1.4", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", + "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + }, + "time": "2025-10-29T12:43:30+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + }, + "time": "2024-12-02T14:37:59+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + }, + "time": "2024-04-29T13:26:35+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.19.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" + }, + "time": "2025-10-17T16:34:55+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "21dc724a0583619cd1652f673303492272778051" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-08-23T21:21:41+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:27:06+00:00" + }, + { + "name": "laravel/framework", + "version": "v12.43.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "195b893593a9298edee177c0844132ebaa02102f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/195b893593a9298edee177c0844132ebaa02102f", + "reference": "195b893593a9298edee177c0844132ebaa02102f", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.7", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/json-schema": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^10.8.1", + "pda/pheanstalk": "^5.0.6|^7.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", + "resend/resend-php": "^0.10.0|^1.0", + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0|^1.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-12-16T18:53:08+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.8", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", + "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.8" + }, + "time": "2025-11-21T20:52:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2025-11-21T20:52:36+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.10.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.10.2" + }, + "time": "2025-11-20T16:29:12+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2025-11-26T21:48:24+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.30.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + }, + "time": "2025-11-10T17:13:11+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.30.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", + "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + }, + "time": "2025-11-10T11:23:37+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/uri", + "version": "7.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.7", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-12-07T16:02:06+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2025-12-07T16:03:21+00:00" + }, + { + "name": "maatwebsite/excel", + "version": "3.1.67", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.30.0", + "psr/simple-cache": "^1.0||^2.0||^3.0" + }, + "require-dev": { + "laravel/scout": "^7.0||^8.0||^9.0||^10.0", + "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + }, + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2025-08-26T09:13:16+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.86", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Mรคnnchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "Andrรกs Kolesรกr", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-12-10T09:58:31+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.11.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", + "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-12-02T21:04:28+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "๐Ÿ“ Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.3" + }, + "time": "2025-10-30T22:57:59+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "๐Ÿ›  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.0" + }, + "time": "2025-12-01T17:49:23+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.3.3", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.3.6" + }, + "require-dev": { + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2025-11-20T02:34:59+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.30.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", + "reference": "fa8257a579ec623473eabfe49731de5967306c4c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": ">=7.4.0 <8.5.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" + }, + "time": "2025-10-26T16:01:04+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.4", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-08-21T11:53:16+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.18", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ddff0ac01beddc251786fe70367cd8bbdb258196", + "reference": "ddff0ac01beddc251786fe70367cd8bbdb258196", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.18" + }, + "time": "2025-12-17T14:35:46+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.9.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + }, + "time": "2025-07-11T13:20:48+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", + "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-12T15:39:26+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-05T15:23:39+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-Franรงois Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-30T13:39:42+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", + "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-05T14:29:59+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-28T09:38:46+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", + "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-05T05:42:40+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bd1af1e425811d6f077db240c3a588bdb405cd27", + "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-07T11:13:10+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6e6f0a5fa8763f75a504b930163785fb6dd055f", + "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-08T07:43:37+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-21T15:26:00+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", + "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-16T10:14:42+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grรฉgoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-16T11:21:06+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "4720254cb2644a0b876233d258a32bf017330db7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", + "reference": "4720254cb2644a0b876233d258a32bf017330db7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", + "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grรฉgoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-25T11:02:55+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", + "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-10-27T20:36:44+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.2", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-04-30T23:37:27+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + } + ], + "packages-dev": [ + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franรงois Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel/breeze", + "version": "v2.3.8", + "source": { + "type": "git", + "url": "https://github.com/laravel/breeze.git", + "reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/breeze/zipball/1a29c5792818bd4cddf70b5f743a227e02fbcfcd", + "reference": "1a29c5792818bd4cddf70b5f743a227e02fbcfcd", + "shasum": "" + }, + "require": { + "illuminate/console": "^11.0|^12.0", + "illuminate/filesystem": "^11.0|^12.0", + "illuminate/support": "^11.0|^12.0", + "illuminate/validation": "^11.0|^12.0", + "php": "^8.2.0", + "symfony/console": "^7.0" + }, + "require-dev": { + "laravel/framework": "^11.0|^12.0", + "orchestra/testbench-core": "^9.0|^10.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Breeze\\BreezeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Breeze\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/breeze/issues", + "source": "https://github.com/laravel/breeze" + }, + "time": "2025-07-18T18:49:59+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/49f92285ff5d6fc09816e976a004f8dec6a0ea30", + "reference": "49f92285ff5d6fc09816e976a004f8dec6a0ea30", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0|^12.0", + "illuminate/contracts": "^10.24|^11.0|^12.0", + "illuminate/log": "^10.24|^11.0|^12.0", + "illuminate/process": "^10.24|^11.0|^12.0", + "illuminate/support": "^10.24|^11.0|^12.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0|^12.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.13|^9.17|^10.8", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", + "phpstan/phpstan": "^1.12.27", + "symfony/var-dumper": "^6.3|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "dev", + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2025-11-20T16:29:35+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", + "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.90.0", + "illuminate/view": "^12.40.1", + "larastan/larastan": "^3.8.0", + "laravel-zero/framework": "^12.0.4", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "dev", + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2025-11-25T21:15:52+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.51.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "1c74357df034e869250b4365dd445c9f6ba5d068" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/1c74357df034e869250b4365dd445c9f6ba5d068", + "reference": "1c74357df034e869250b4365dd445c9f6ba5d068", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "phpstan/phpstan": "^2.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2025-12-09T13:33:49+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pรกdraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.8.3", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", + "php": "^8.2.0", + "symfony/console": "^7.3.0" + }, + "conflict": { + "laravel/framework": "<11.44.2 || >=13.0.0", + "phpunit/phpunit": "<11.5.15 || >=13.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.3", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2 || ^4.0.0", + "sebastian/environment": "^7.2.1 || ^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-11-20T02:55:25+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-08-27T14:37:49+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-27T05:02:59+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.46", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/75dfe79a2aa30085b7132bb84377c24062193f33", + "reference": "75dfe79a2aa30085b7132bb84377c24062193f33", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.46" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-12-06T08:01:15+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-10T08:07:46+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-04T18:11:45+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..423eed5 --- /dev/null +++ b/config/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..7d1eb0d --- /dev/null +++ b/config/auth.php @@ -0,0 +1,115 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', App\Models\User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the number of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..b32aead --- /dev/null +++ b/config/cache.php @@ -0,0 +1,117 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", + | "failover", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + 'failover' => [ + 'driver' => 'failover', + 'stores' => [ + 'database', + 'array', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..c57fa63 --- /dev/null +++ b/config/database.php @@ -0,0 +1,183 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..3d671bd --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..9e998a4 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'handler_with' => [ + 'stream' => 'php://stderr', + ], + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..522b284 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,118 @@ + env('MAIL_MAILER', 'log'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "resend", "log", "array", + | "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + 'retry_after' => 60, + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..79c2c0a --- /dev/null +++ b/config/queue.php @@ -0,0 +1,129 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", + | "deferred", "background", "failover", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + 'deferred' => [ + 'driver' => 'deferred', + ], + + 'background' => [ + 'driver' => 'background', + ], + + 'failover' => [ + 'driver' => 'failover', + 'connections' => [ + 'database', + 'deferred', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..6a90eb8 --- /dev/null +++ b/config/services.php @@ -0,0 +1,38 @@ + [ + 'key' => env('POSTMARK_API_KEY'), + ], + + 'resend' => [ + 'key' => env('RESEND_API_KEY'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..5b541b7 --- /dev/null +++ b/config/session.php @@ -0,0 +1,217 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug((string) env('APP_NAME', 'laravel')).'-session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain without subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..584104c --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,44 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100644 index 0000000..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100644 index 0000000..b9c106b --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration'); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100644 index 0000000..425e705 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2024_01_01_000003_create_kategoris_table.php b/database/migrations/2024_01_01_000003_create_kategoris_table.php new file mode 100644 index 0000000..3574cb9 --- /dev/null +++ b/database/migrations/2024_01_01_000003_create_kategoris_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('nama'); + $table->string('keterangan')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('kategoris'); + } +}; diff --git a/database/migrations/2024_01_01_000004_create_suppliers_table.php b/database/migrations/2024_01_01_000004_create_suppliers_table.php new file mode 100644 index 0000000..fe468b6 --- /dev/null +++ b/database/migrations/2024_01_01_000004_create_suppliers_table.php @@ -0,0 +1,25 @@ +id(); + $table->string('nama'); + $table->text('alamat')->nullable(); + $table->string('telepon')->nullable(); + $table->string('email')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('suppliers'); + } +}; diff --git a/database/migrations/2024_01_01_000005_create_obats_table.php b/database/migrations/2024_01_01_000005_create_obats_table.php new file mode 100644 index 0000000..2f8d402 --- /dev/null +++ b/database/migrations/2024_01_01_000005_create_obats_table.php @@ -0,0 +1,26 @@ +id(); + $table->foreignId('kategori_id')->constrained('kategoris')->onDelete('cascade'); + $table->string('nama'); + $table->string('kode')->unique(); + $table->string('satuan'); + $table->text('deskripsi')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('obats'); + } +}; diff --git a/database/migrations/2024_01_01_000006_create_obat_masuks_table.php b/database/migrations/2024_01_01_000006_create_obat_masuks_table.php new file mode 100644 index 0000000..851279b --- /dev/null +++ b/database/migrations/2024_01_01_000006_create_obat_masuks_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('obat_id')->constrained('obats')->onDelete('cascade'); + $table->foreignId('supplier_id')->nullable()->constrained('suppliers')->onDelete('set null'); + $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); + $table->string('kode_batch'); + $table->integer('stok'); + $table->decimal('harga_beli', 12, 2); + $table->decimal('harga_jual', 12, 2); + $table->date('tanggal_penerimaan'); + $table->date('tanggal_kadaluarsa'); + $table->string('no_faktur')->nullable(); + $table->text('catatan')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('obat_masuks'); + } +}; diff --git a/database/migrations/2024_01_01_000007_create_obat_keluars_table.php b/database/migrations/2024_01_01_000007_create_obat_keluars_table.php new file mode 100644 index 0000000..b0b16e7 --- /dev/null +++ b/database/migrations/2024_01_01_000007_create_obat_keluars_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('obat_id')->constrained('obats')->onDelete('cascade'); + $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); + $table->string('kode_batch'); + $table->integer('jumlah'); + $table->date('tanggal_pengeluaran'); + $table->date('tanggal_kadaluarsa'); + $table->string('no_pengeluaran')->nullable(); + $table->string('nama_petugas'); + $table->string('nama_penerima'); + $table->string('tujuan')->nullable(); + $table->text('keterangan')->nullable(); + $table->enum('status', ['terkirim', 'proses', 'dibatalkan'])->default('proses'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('obat_keluars'); + } +}; diff --git a/database/migrations/2024_01_01_000008_create_satuans_table.php b/database/migrations/2024_01_01_000008_create_satuans_table.php new file mode 100644 index 0000000..5d5e471 --- /dev/null +++ b/database/migrations/2024_01_01_000008_create_satuans_table.php @@ -0,0 +1,23 @@ +id(); + $table->string('nama'); + $table->string('keterangan')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('satuans'); + } +}; diff --git a/database/migrations/2024_01_01_000009_add_fields_to_obat_masuks_table.php b/database/migrations/2024_01_01_000009_add_fields_to_obat_masuks_table.php new file mode 100644 index 0000000..fa69466 --- /dev/null +++ b/database/migrations/2024_01_01_000009_add_fields_to_obat_masuks_table.php @@ -0,0 +1,29 @@ +string('nama_obat', 200)->after('id'); + $table->foreignId('kategori_id')->nullable()->after('nama_obat')->constrained('kategoris')->nullOnDelete(); + $table->foreignId('satuan_id')->nullable()->after('kategori_id')->constrained('satuans')->nullOnDelete(); + + // Make obat_id nullable as we're using direct input now + $table->foreignId('obat_id')->nullable()->change(); + }); + } + + public function down(): void + { + Schema::table('obat_masuks', function (Blueprint $table) { + $table->dropForeign(['kategori_id']); + $table->dropForeign(['satuan_id']); + $table->dropColumn(['nama_obat', 'kategori_id', 'satuan_id']); + }); + } +}; diff --git a/database/migrations/2025_12_20_151320_add_nama_supplier_to_obat_masuks_table.php b/database/migrations/2025_12_20_151320_add_nama_supplier_to_obat_masuks_table.php new file mode 100644 index 0000000..6ac8928 --- /dev/null +++ b/database/migrations/2025_12_20_151320_add_nama_supplier_to_obat_masuks_table.php @@ -0,0 +1,28 @@ +string('nama_supplier')->nullable()->after('satuan_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('obat_masuks', function (Blueprint $table) { + $table->dropColumn('nama_supplier'); + }); + } +}; diff --git a/database/migrations/2025_12_20_152837_update_obat_keluars_use_obat_masuk.php b/database/migrations/2025_12_20_152837_update_obat_keluars_use_obat_masuk.php new file mode 100644 index 0000000..7044dcb --- /dev/null +++ b/database/migrations/2025_12_20_152837_update_obat_keluars_use_obat_masuk.php @@ -0,0 +1,33 @@ +foreignId('obat_masuk_id')->nullable()->after('id')->constrained('obat_masuks')->nullOnDelete(); + $table->string('nama_obat')->nullable()->after('obat_masuk_id'); + $table->renameColumn('keterangan', 'catatan'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('obat_keluars', function (Blueprint $table) { + $table->dropForeign(['obat_masuk_id']); + $table->dropColumn('obat_masuk_id'); + $table->dropColumn('nama_obat'); + $table->renameColumn('catatan', 'keterangan'); + }); + } +}; diff --git a/database/migrations/2025_12_20_154137_add_tujuan_pemakaian_to_obat_keluars_table.php b/database/migrations/2025_12_20_154137_add_tujuan_pemakaian_to_obat_keluars_table.php new file mode 100644 index 0000000..eb33d3d --- /dev/null +++ b/database/migrations/2025_12_20_154137_add_tujuan_pemakaian_to_obat_keluars_table.php @@ -0,0 +1,28 @@ +string('tujuan_pemakaian')->nullable()->after('jumlah'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('obat_keluars', function (Blueprint $table) { + $table->dropColumn('tujuan_pemakaian'); + }); + } +}; diff --git a/database/migrations/2025_12_20_155815_make_obat_id_nullable_on_obat_keluars.php b/database/migrations/2025_12_20_155815_make_obat_id_nullable_on_obat_keluars.php new file mode 100644 index 0000000..7a223f6 --- /dev/null +++ b/database/migrations/2025_12_20_155815_make_obat_id_nullable_on_obat_keluars.php @@ -0,0 +1,28 @@ +unsignedBigInteger('obat_id')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('obat_keluars', function (Blueprint $table) { + $table->unsignedBigInteger('obat_id')->nullable(false)->change(); + }); + } +}; diff --git a/database/migrations/2026_02_01_000001_add_role_to_users_table.php b/database/migrations/2026_02_01_000001_add_role_to_users_table.php new file mode 100644 index 0000000..6d38347 --- /dev/null +++ b/database/migrations/2026_02_01_000001_add_role_to_users_table.php @@ -0,0 +1,28 @@ +enum('role', ['dokter', 'apoteker'])->default('apoteker')->after('email'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('role'); + }); + } +}; diff --git a/database/migrations/2026_02_01_000002_create_reseps_table.php b/database/migrations/2026_02_01_000002_create_reseps_table.php new file mode 100644 index 0000000..6c37d31 --- /dev/null +++ b/database/migrations/2026_02_01_000002_create_reseps_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('no_resep')->unique(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('nama_pasien'); + $table->integer('umur_pasien')->nullable(); + $table->string('alamat_pasien')->nullable(); + $table->date('tanggal_resep'); + $table->text('diagnosa')->nullable(); + $table->text('catatan')->nullable(); + $table->enum('status', ['draft', 'selesai', 'dibatalkan'])->default('selesai'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('reseps'); + } +}; diff --git a/database/migrations/2026_02_01_000003_create_resep_items_table.php b/database/migrations/2026_02_01_000003_create_resep_items_table.php new file mode 100644 index 0000000..4c3a075 --- /dev/null +++ b/database/migrations/2026_02_01_000003_create_resep_items_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('resep_id')->constrained()->onDelete('cascade'); + $table->foreignId('obat_masuk_id')->constrained()->onDelete('cascade'); + $table->string('nama_obat'); + $table->integer('jumlah'); + $table->string('aturan_pakai')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('resep_items'); + } +}; diff --git a/database/migrations/2026_02_08_000001_update_reseps_table.php b/database/migrations/2026_02_08_000001_update_reseps_table.php new file mode 100644 index 0000000..ae5b6c6 --- /dev/null +++ b/database/migrations/2026_02_08_000001_update_reseps_table.php @@ -0,0 +1,61 @@ +string('nama_dokter')->nullable()->after('user_id'); + $table->string('no_sip')->nullable()->after('nama_dokter'); + $table->enum('jenis_penjamin', ['umum', 'BPJS Kes', 'BPJS Naker', 'Jamkesmas/KIS', 'R. Inap'])->default('umum')->after('no_resep'); + $table->enum('jenis_layanan', ['BP', 'KIA', 'Gigi', 'UGD', 'Lainnya'])->default('BP')->after('jenis_penjamin'); + + // Data Pasien + $table->string('no_rm')->nullable()->after('nama_pasien'); + $table->enum('jenis_kelamin', ['L', 'P'])->nullable()->after('alamat_pasien'); + $table->decimal('berat_badan', 5, 2)->nullable()->after('umur_pasien'); + + // Notification + $table->boolean('is_read')->default(false)->after('status'); + }); + + // Update existing status values and change enum + // First update any 'draft' to 'proses', then modify enum + DB::statement("UPDATE reseps SET status = 'proses' WHERE status = 'draft'"); + + // Change enum values + DB::statement("ALTER TABLE reseps MODIFY COLUMN status ENUM('proses', 'selesai', 'dibatalkan') DEFAULT 'proses'"); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Revert status enum + DB::statement("ALTER TABLE reseps MODIFY COLUMN status ENUM('draft', 'selesai', 'dibatalkan') DEFAULT 'selesai'"); + DB::statement("UPDATE reseps SET status = 'draft' WHERE status = 'proses'"); + + Schema::table('reseps', function (Blueprint $table) { + $table->dropColumn([ + 'nama_dokter', + 'no_sip', + 'jenis_penjamin', + 'jenis_layanan', + 'no_rm', + 'jenis_kelamin', + 'berat_badan', + 'is_read', + ]); + }); + } +}; diff --git a/database/migrations/2026_02_08_000002_update_obat_masuks_table.php b/database/migrations/2026_02_08_000002_update_obat_masuks_table.php new file mode 100644 index 0000000..69459ed --- /dev/null +++ b/database/migrations/2026_02_08_000002_update_obat_masuks_table.php @@ -0,0 +1,34 @@ +renameColumn('nama_supplier', 'sumber_dana'); + + // Add new fields + $table->string('barcode')->nullable()->after('kode_batch'); + $table->string('no_sbbk')->nullable()->after('no_faktur'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('obat_masuks', function (Blueprint $table) { + $table->dropColumn(['barcode', 'no_sbbk']); + $table->renameColumn('sumber_dana', 'nama_supplier'); + }); + } +}; diff --git a/database/migrations/2026_02_08_000003_update_obat_keluars_table.php b/database/migrations/2026_02_08_000003_update_obat_keluars_table.php new file mode 100644 index 0000000..b029a18 --- /dev/null +++ b/database/migrations/2026_02_08_000003_update_obat_keluars_table.php @@ -0,0 +1,54 @@ +string('barcode')->nullable()->after('kode_batch'); + } + if (!Schema::hasColumn('obat_keluars', 'sumber_dana')) { + $table->string('sumber_dana')->nullable()->after('tujuan_pemakaian'); + } + if (!Schema::hasColumn('obat_keluars', 'harga')) { + $table->decimal('harga', 15, 2)->nullable()->after('jumlah'); + } + if (!Schema::hasColumn('obat_keluars', 'harga_total')) { + $table->decimal('harga_total', 15, 2)->nullable()->after('harga'); + } + }); + + // First, expand the enum to include all values (both old and new) + DB::statement("ALTER TABLE obat_keluars MODIFY COLUMN status ENUM('terkirim', 'proses', 'selesai', 'dibatalkan') DEFAULT 'proses'"); + + // Update existing 'terkirim' status to 'selesai' + DB::statement("UPDATE obat_keluars SET status = 'selesai' WHERE status = 'terkirim'"); + + // Finally, narrow enum to only new values + DB::statement("ALTER TABLE obat_keluars MODIFY COLUMN status ENUM('proses', 'selesai', 'dibatalkan') DEFAULT 'proses'"); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Revert status enum + DB::statement("ALTER TABLE obat_keluars MODIFY COLUMN status ENUM('terkirim', 'proses', 'dibatalkan') DEFAULT 'proses'"); + DB::statement("UPDATE obat_keluars SET status = 'terkirim' WHERE status = 'selesai'"); + + Schema::table('obat_keluars', function (Blueprint $table) { + $table->dropColumn(['barcode', 'sumber_dana', 'harga', 'harga_total']); + }); + } +}; diff --git a/database/migrations/2026_02_10_104449_make_harga_beli_nullable_in_obat_masuks_table.php b/database/migrations/2026_02_10_104449_make_harga_beli_nullable_in_obat_masuks_table.php new file mode 100644 index 0000000..d65411c --- /dev/null +++ b/database/migrations/2026_02_10_104449_make_harga_beli_nullable_in_obat_masuks_table.php @@ -0,0 +1,24 @@ +decimal('harga_beli', 12, 2)->nullable()->default(0)->change(); + $table->decimal('harga_jual', 12, 2)->nullable()->default(0)->change(); + }); + } + + public function down(): void + { + Schema::table('obat_masuks', function (Blueprint $table) { + $table->decimal('harga_beli', 12, 2)->nullable(false)->default(null)->change(); + $table->decimal('harga_jual', 12, 2)->nullable(false)->default(null)->change(); + }); + } +}; diff --git a/database/migrations/2026_02_21_000004_add_profile_fields_to_users_table.php b/database/migrations/2026_02_21_000004_add_profile_fields_to_users_table.php new file mode 100644 index 0000000..4ab9029 --- /dev/null +++ b/database/migrations/2026_02_21_000004_add_profile_fields_to_users_table.php @@ -0,0 +1,42 @@ +string('nip')->nullable()->after('name'); + $table->string('phone')->nullable()->after('email'); + $table->string('gender')->nullable()->after('phone'); + $table->text('address')->nullable()->after('gender'); + $table->string('division')->nullable()->after('address'); + $table->string('position')->nullable()->after('division'); + $table->string('profile_photo')->nullable()->after('position'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn([ + 'nip', + 'phone', + 'gender', + 'address', + 'division', + 'position', + 'profile_photo', + ]); + }); + } +}; diff --git a/database/migrations/2026_05_05_110420_add_superadmin_role_to_users_table.php b/database/migrations/2026_05_05_110420_add_superadmin_role_to_users_table.php new file mode 100644 index 0000000..a41a2ce --- /dev/null +++ b/database/migrations/2026_05_05_110420_add_superadmin_role_to_users_table.php @@ -0,0 +1,26 @@ +id(); + $table->string('name'); + $table->string('nip')->nullable(); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->string('phone')->nullable(); + $table->string('gender')->nullable(); + $table->string('address')->nullable(); + $table->string('division')->nullable(); + $table->string('position')->nullable(); + $table->string('profile_photo')->nullable(); + $table->string('role')->nullable()->default(null); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('kategoris', function (Blueprint $table) { + $table->id(); + $table->string('nama')->unique(); + $table->string('keterangan')->nullable(); + $table->timestamps(); + }); + + Schema::create('suppliers', function (Blueprint $table) { + $table->id(); + $table->string('nama'); + $table->string('alamat')->nullable(); + $table->string('telepon')->nullable(); + $table->string('email')->nullable(); + $table->timestamps(); + }); + + Schema::create('satuans', function (Blueprint $table) { + $table->id(); + $table->string('nama')->unique(); + $table->string('keterangan')->nullable(); + $table->timestamps(); + }); + + Schema::create('obats', function (Blueprint $table) { + $table->id(); + $table->string('nama'); + $table->foreignId('kategori_id')->nullable()->constrained('kategoris')->onDelete('set null'); + $table->timestamps(); + }); + + Schema::create('obat_masuks', function (Blueprint $table) { + $table->id(); + $table->foreignId('obat_id')->nullable()->constrained('obats')->onDelete('cascade'); + $table->foreignId('kategori_id')->nullable()->constrained('kategoris')->onDelete('set null'); + $table->foreignId('satuan_id')->nullable()->constrained('satuans')->onDelete('set null'); + $table->foreignId('supplier_id')->nullable()->constrained('suppliers')->onDelete('set null'); + $table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null'); + $table->string('nama_obat')->nullable(); + $table->string('kode_batch'); + $table->string('barcode')->nullable(); + $table->integer('stok')->default(0); + $table->decimal('harga_beli', 12, 2)->nullable(); + $table->decimal('harga_jual', 12, 2)->nullable(); + $table->string('sumber_dana')->nullable(); + $table->string('no_sbbk')->nullable(); + $table->date('tanggal_penerimaan'); + $table->date('tanggal_kadaluarsa'); + $table->string('no_faktur')->nullable(); + $table->string('nama_supplier')->nullable(); + $table->text('catatan')->nullable(); + $table->timestamps(); + }); + + Schema::create('obat_keluars', function (Blueprint $table) { + $table->id(); + $table->foreignId('obat_masuk_id')->nullable()->constrained('obat_masuks')->onDelete('set null'); + $table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null'); + $table->string('nama_obat')->nullable(); + $table->string('kode_batch')->nullable(); + $table->string('barcode')->nullable(); + $table->integer('jumlah'); + $table->decimal('harga', 15, 2)->nullable(); + $table->decimal('harga_total', 15, 2)->nullable(); + $table->string('tujuan_pemakaian')->nullable(); + $table->string('sumber_dana')->nullable(); + $table->date('tanggal_pengeluaran'); + $table->date('tanggal_kadaluarsa')->nullable(); + $table->string('no_pengeluaran')->nullable(); + $table->string('nama_petugas')->nullable(); + $table->string('nama_penerima')->nullable(); + $table->text('catatan')->nullable(); + // SQLite tidak support ENUM, gunakan string + $table->string('status')->default('proses'); + $table->timestamps(); + }); + + Schema::create('reseps', function (Blueprint $table) { + $table->id(); + $table->string('no_resep')->unique(); + $table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null'); + $table->string('nama_dokter')->nullable(); + $table->string('no_sip')->nullable(); + $table->string('jenis_penjamin')->default('umum'); + $table->string('jenis_layanan')->default('BP'); + $table->string('nama_pasien'); + $table->string('no_rm')->nullable(); + $table->integer('umur_pasien')->nullable(); + $table->decimal('berat_badan', 5, 2)->nullable(); + $table->string('alamat_pasien')->nullable(); + $table->string('jenis_kelamin')->nullable(); + $table->date('tanggal_resep'); + $table->text('diagnosa')->nullable(); + $table->text('catatan')->nullable(); + // SQLite tidak support ENUM, gunakan string + $table->string('status')->default('proses'); + $table->boolean('is_read')->default(false); + $table->timestamps(); + }); + + Schema::create('resep_items', function (Blueprint $table) { + $table->id(); + $table->foreignId('resep_id')->constrained('reseps')->onDelete('cascade'); + $table->foreignId('obat_masuk_id')->nullable()->constrained('obat_masuks')->onDelete('set null'); + $table->string('nama_obat')->nullable(); + $table->integer('jumlah'); + $table->string('aturan_pakai')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('resep_items'); + Schema::dropIfExists('reseps'); + Schema::dropIfExists('obat_keluars'); + Schema::dropIfExists('obat_masuks'); + Schema::dropIfExists('obats'); + Schema::dropIfExists('satuans'); + Schema::dropIfExists('suppliers'); + Schema::dropIfExists('kategoris'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('users'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..652c413 --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,126 @@ +call([ + SuperAdminSeeder::class, + ]); + + // Create admin user (apoteker) + $user = User::create([ + 'name' => 'Admin', + 'email' => 'admin@admin.com', + 'password' => Hash::make('password'), + 'role' => 'apoteker', + ]); + + // Create dokter user + User::create([ + 'name' => 'Dokter', + 'email' => 'dokter@admin.com', + 'password' => Hash::make('password'), + 'role' => 'dokter', + ]); + + // Create categories + $kategoris = [ + ['nama' => 'Analgesik', 'keterangan' => 'Obat pereda nyeri'], + ['nama' => 'Antibiotik', 'keterangan' => 'Obat anti bakteri'], + ['nama' => 'Antipiretik', 'keterangan' => 'Obat penurun demam'], + ['nama' => 'Antihipertensi', 'keterangan' => 'Obat tekanan darah tinggi'], + ['nama' => 'Antidiabetik', 'keterangan' => 'Obat diabetes'], + ['nama' => 'Vitamin dan Suplemen', 'keterangan' => 'Suplemen kesehatan'], + ]; + + foreach ($kategoris as $kategori) { + Kategori::create($kategori); + } + + // Create satuans (units) + $satuans = [ + ['nama' => 'Tablet', 'keterangan' => 'Obat dalam bentuk tablet'], + ['nama' => 'Kapsul', 'keterangan' => 'Obat dalam bentuk kapsul'], + ['nama' => 'Botol', 'keterangan' => 'Obat cair dalam botol'], + ['nama' => 'Strip', 'keterangan' => 'Obat dalam kemasan strip'], + ['nama' => 'Box', 'keterangan' => 'Obat dalam kemasan box'], + ['nama' => 'Ampul', 'keterangan' => 'Obat injeksi dalam ampul'], + ['nama' => 'Tube', 'keterangan' => 'Obat salep/krim dalam tube'], + ]; + + foreach ($satuans as $satuan) { + Satuan::create($satuan); + } + + // Create suppliers + $suppliers = [ + ['nama' => 'PT Kimia Farma', 'alamat' => 'Jakarta Pusat', 'telepon' => '021-1234567'], + ['nama' => 'PT Kalbe Farma', 'alamat' => 'Jakarta Timur', 'telepon' => '021-7654321'], + ['nama' => 'PT Sanbe Farma', 'alamat' => 'Bandung', 'telepon' => '022-1234567'], + ]; + + foreach ($suppliers as $supplier) { + Supplier::create($supplier); + } + + // Create medicines (legacy - keep for reference) + $obats = [ + ['kategori_id' => 1, 'nama' => 'Paracetamol 500mg', 'kode' => 'OBT-001', 'satuan' => 'Tablet'], + ['kategori_id' => 1, 'nama' => 'Ibuprofen 400mg', 'kode' => 'OBT-002', 'satuan' => 'Tablet'], + ['kategori_id' => 2, 'nama' => 'Amoxicillin 500mg', 'kode' => 'OBT-003', 'satuan' => 'Kapsul'], + ['kategori_id' => 2, 'nama' => 'Ciprofloxacin 500mg', 'kode' => 'OBT-004', 'satuan' => 'Tablet'], + ['kategori_id' => 3, 'nama' => 'Aspirin 100mg', 'kode' => 'OBT-005', 'satuan' => 'Tablet'], + ['kategori_id' => 4, 'nama' => 'Amlodipine 5mg', 'kode' => 'OBT-006', 'satuan' => 'Tablet'], + ['kategori_id' => 4, 'nama' => 'Captopril 25mg', 'kode' => 'OBT-007', 'satuan' => 'Tablet'], + ['kategori_id' => 5, 'nama' => 'Metformin 500mg', 'kode' => 'OBT-008', 'satuan' => 'Tablet'], + ['kategori_id' => 6, 'nama' => 'Vitamin C 1000mg', 'kode' => 'OBT-009', 'satuan' => 'Tablet'], + ['kategori_id' => 6, 'nama' => 'Vitamin D3 1000IU', 'kode' => 'OBT-010', 'satuan' => 'Kapsul'], + ]; + + foreach ($obats as $obat) { + Obat::create($obat); + } + + // Create incoming medicines with new structure + $obatMasuks = [ + ['nama_obat' => 'Paracetamol 500mg', 'kategori_id' => 1, 'satuan_id' => 1, 'obat_id' => 1, 'supplier_id' => 1, 'user_id' => 1, 'kode_batch' => 'BCH-2024-001', 'stok' => 100, 'harga_beli' => 1500, 'harga_jual' => 2500, 'tanggal_penerimaan' => Carbon::now()->subDays(30), 'tanggal_kadaluarsa' => Carbon::now()->addMonths(6), 'no_faktur' => 'FKT-001'], + ['nama_obat' => 'Ibuprofen 400mg', 'kategori_id' => 1, 'satuan_id' => 1, 'obat_id' => 2, 'supplier_id' => 1, 'user_id' => 1, 'kode_batch' => 'BCH-2024-002', 'stok' => 80, 'harga_beli' => 2000, 'harga_jual' => 3500, 'tanggal_penerimaan' => Carbon::now()->subDays(25), 'tanggal_kadaluarsa' => Carbon::now()->addDays(45), 'no_faktur' => 'FKT-002'], + ['nama_obat' => 'Amoxicillin 500mg', 'kategori_id' => 2, 'satuan_id' => 2, 'obat_id' => 3, 'supplier_id' => 2, 'user_id' => 1, 'kode_batch' => 'BCH-2024-003', 'stok' => 150, 'harga_beli' => 3000, 'harga_jual' => 5000, 'tanggal_penerimaan' => Carbon::now()->subDays(20), 'tanggal_kadaluarsa' => Carbon::now()->addMonths(12), 'no_faktur' => 'FKT-003'], + ['nama_obat' => 'Ciprofloxacin 500mg', 'kategori_id' => 2, 'satuan_id' => 1, 'obat_id' => 4, 'supplier_id' => 2, 'user_id' => 1, 'kode_batch' => 'BCH-2024-004', 'stok' => 60, 'harga_beli' => 4000, 'harga_jual' => 6500, 'tanggal_penerimaan' => Carbon::now()->subDays(15), 'tanggal_kadaluarsa' => Carbon::now()->addDays(20), 'no_faktur' => 'FKT-004'], + ['nama_obat' => 'Aspirin 100mg', 'kategori_id' => 3, 'satuan_id' => 1, 'obat_id' => 5, 'supplier_id' => 3, 'user_id' => 1, 'kode_batch' => 'BCH-2024-005', 'stok' => 200, 'harga_beli' => 1000, 'harga_jual' => 1800, 'tanggal_penerimaan' => Carbon::now()->subDays(10), 'tanggal_kadaluarsa' => Carbon::now()->addDays(75), 'no_faktur' => 'FKT-005'], + ['nama_obat' => 'Amlodipine 5mg', 'kategori_id' => 4, 'satuan_id' => 1, 'obat_id' => 6, 'supplier_id' => 1, 'user_id' => 1, 'kode_batch' => 'BCH-2024-006', 'stok' => 120, 'harga_beli' => 2500, 'harga_jual' => 4000, 'tanggal_penerimaan' => Carbon::now()->subDays(5), 'tanggal_kadaluarsa' => Carbon::now()->addMonths(8), 'no_faktur' => 'FKT-006'], + ['nama_obat' => 'Captopril 25mg', 'kategori_id' => 4, 'satuan_id' => 1, 'obat_id' => 7, 'supplier_id' => 2, 'user_id' => 1, 'kode_batch' => 'BCH-2024-007', 'stok' => 90, 'harga_beli' => 1800, 'harga_jual' => 3000, 'tanggal_penerimaan' => Carbon::now()->subDays(3), 'tanggal_kadaluarsa' => Carbon::now()->addDays(15), 'no_faktur' => 'FKT-007'], + ]; + + foreach ($obatMasuks as $obatMasuk) { + ObatMasuk::create($obatMasuk); + } + + // Create outgoing medicines + $obatKeluars = [ + ['obat_id' => 1, 'user_id' => 1, 'kode_batch' => 'BCH-2024-001', 'jumlah' => 20, 'tanggal_pengeluaran' => Carbon::now()->subDays(5), 'tanggal_kadaluarsa' => Carbon::now()->addMonths(6), 'no_pengeluaran' => 'OUT-001', 'nama_petugas' => 'Admin', 'nama_penerima' => 'Apotek Sehat', 'tujuan' => 'Distribusi', 'status' => 'terkirim'], + ['obat_id' => 2, 'user_id' => 1, 'kode_batch' => 'BCH-2024-002', 'jumlah' => 15, 'tanggal_pengeluaran' => Carbon::now()->subDays(4), 'tanggal_kadaluarsa' => Carbon::now()->addDays(45), 'no_pengeluaran' => 'OUT-002', 'nama_petugas' => 'Admin', 'nama_penerima' => 'Puskesmas Maju', 'tujuan' => 'Distribusi', 'status' => 'terkirim'], + ['obat_id' => 3, 'user_id' => 1, 'kode_batch' => 'BCH-2024-003', 'jumlah' => 30, 'tanggal_pengeluaran' => Carbon::now()->subDays(3), 'tanggal_kadaluarsa' => Carbon::now()->addMonths(12), 'no_pengeluaran' => 'OUT-003', 'nama_petugas' => 'Admin', 'nama_penerima' => 'RS Harapan', 'tujuan' => 'Distribusi', 'status' => 'proses'], + ['obat_id' => 5, 'user_id' => 1, 'kode_batch' => 'BCH-2024-005', 'jumlah' => 50, 'tanggal_pengeluaran' => Carbon::now()->subDays(2), 'tanggal_kadaluarsa' => Carbon::now()->addDays(75), 'no_pengeluaran' => 'OUT-004', 'nama_petugas' => 'Admin', 'nama_penerima' => 'Klinik Pratama', 'tujuan' => 'Distribusi', 'status' => 'terkirim'], + ]; + + foreach ($obatKeluars as $obatKeluar) { + ObatKeluar::create($obatKeluar); + } + } +} diff --git a/database/seeders/SuperAdminSeeder.php b/database/seeders/SuperAdminSeeder.php new file mode 100644 index 0000000..08d2b8b --- /dev/null +++ b/database/seeders/SuperAdminSeeder.php @@ -0,0 +1,25 @@ + 'superadmin@meddata.com'], + [ + 'name' => 'Super Admin', + 'password' => Hash::make('password'), + 'role' => 'superadmin', + ] + ); + } +} diff --git a/medorymy_main new db.sql b/medorymy_main new db.sql new file mode 100644 index 0000000..7dfa995 --- /dev/null +++ b/medorymy_main new db.sql @@ -0,0 +1,732 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.2 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Waktu pembuatan: 24 Feb 2026 pada 11.00 +-- Versi server: 10.6.25-MariaDB +-- Versi PHP: 8.4.16 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `medorymy_main` +-- + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `cache` +-- + +CREATE TABLE `cache` ( + `key` varchar(255) NOT NULL, + `value` mediumtext NOT NULL, + `expiration` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `cache_locks` +-- + +CREATE TABLE `cache_locks` ( + `key` varchar(255) NOT NULL, + `owner` varchar(255) NOT NULL, + `expiration` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `failed_jobs` +-- + +CREATE TABLE `failed_jobs` ( + `id` bigint(20) UNSIGNED NOT NULL, + `uuid` varchar(255) NOT NULL, + `connection` text NOT NULL, + `queue` text NOT NULL, + `payload` longtext NOT NULL, + `exception` longtext NOT NULL, + `failed_at` timestamp NOT NULL DEFAULT current_timestamp() +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `jobs` +-- + +CREATE TABLE `jobs` ( + `id` bigint(20) UNSIGNED NOT NULL, + `queue` varchar(255) NOT NULL, + `payload` longtext NOT NULL, + `attempts` tinyint(3) UNSIGNED NOT NULL, + `reserved_at` int(10) UNSIGNED DEFAULT NULL, + `available_at` int(10) UNSIGNED NOT NULL, + `created_at` int(10) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `job_batches` +-- + +CREATE TABLE `job_batches` ( + `id` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `total_jobs` int(11) NOT NULL, + `pending_jobs` int(11) NOT NULL, + `failed_jobs` int(11) NOT NULL, + `failed_job_ids` longtext NOT NULL, + `options` mediumtext DEFAULT NULL, + `cancelled_at` int(11) DEFAULT NULL, + `created_at` int(11) NOT NULL, + `finished_at` int(11) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `kategoris` +-- + +CREATE TABLE `kategoris` ( + `id` bigint(20) UNSIGNED NOT NULL, + `nama` varchar(255) NOT NULL, + `keterangan` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `kategoris` +-- + +INSERT INTO `kategoris` (`id`, `nama`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 'Analgesik', 'Obat pereda nyeri', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, 'Antibiotik', 'Obat anti bakteri', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(3, 'Antipiretik', 'Obat penurun demam', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(4, 'Antihipertensi', 'Obat tekanan darah tinggi', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(5, 'Antidiabetik', 'Obat diabetes', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(6, 'Vitamin dan Suplemen', 'Suplemen kesehatan', '2025-12-24 04:52:26', '2025-12-24 04:52:26'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `migrations` +-- + +CREATE TABLE `migrations` ( + `id` int(10) UNSIGNED NOT NULL, + `migration` varchar(255) NOT NULL, + `batch` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `migrations` +-- + +INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES +(1, '0001_01_01_000000_create_users_table', 1), +(2, '0001_01_01_000001_create_cache_table', 1), +(3, '0001_01_01_000002_create_jobs_table', 1), +(4, '2024_01_01_000003_create_kategoris_table', 1), +(5, '2024_01_01_000004_create_suppliers_table', 1), +(6, '2024_01_01_000005_create_obats_table', 1), +(7, '2024_01_01_000006_create_obat_masuks_table', 1), +(8, '2024_01_01_000007_create_obat_keluars_table', 1), +(9, '2024_01_01_000008_create_satuans_table', 1), +(10, '2024_01_01_000009_add_fields_to_obat_masuks_table', 1), +(11, '2025_12_20_151320_add_nama_supplier_to_obat_masuks_table', 1), +(12, '2025_12_20_152837_update_obat_keluars_use_obat_masuk', 1), +(13, '2025_12_20_154137_add_tujuan_pemakaian_to_obat_keluars_table', 1), +(14, '2025_12_20_155815_make_obat_id_nullable_on_obat_keluars', 1), +(15, '2026_02_01_000001_add_role_to_users_table', 2), +(16, '2026_02_01_000002_create_reseps_table', 3), +(17, '2026_02_01_000003_create_resep_items_table', 4), +(18, '2026_02_08_000001_update_reseps_table', 5), +(19, '2026_02_08_000002_update_obat_masuks_table', 6), +(20, '2026_02_08_000003_update_obat_keluars_table', 7), +(21, '2026_02_10_104449_make_harga_beli_nullable_in_obat_masuks_table', 8), +(22, '2026_02_21_000004_add_profile_fields_to_users_table', 9); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obats` +-- + +CREATE TABLE `obats` ( + `id` bigint(20) UNSIGNED NOT NULL, + `kategori_id` bigint(20) UNSIGNED NOT NULL, + `nama` varchar(255) NOT NULL, + `kode` varchar(255) NOT NULL, + `satuan` varchar(255) NOT NULL, + `deskripsi` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obats` +-- + +INSERT INTO `obats` (`id`, `kategori_id`, `nama`, `kode`, `satuan`, `deskripsi`, `created_at`, `updated_at`) VALUES +(1, 1, 'Paracetamol 500mg', 'OBT-001', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, 1, 'Ibuprofen 400mg', 'OBT-002', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(3, 2, 'Amoxicillin 500mg', 'OBT-003', 'Kapsul', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(4, 2, 'Ciprofloxacin 500mg', 'OBT-004', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(5, 3, 'Aspirin 100mg', 'OBT-005', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(6, 4, 'Amlodipine 5mg', 'OBT-006', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(7, 4, 'Captopril 25mg', 'OBT-007', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(8, 5, 'Metformin 500mg', 'OBT-008', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(9, 6, 'Vitamin C 1000mg', 'OBT-009', 'Tablet', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(10, 6, 'Vitamin D3 1000IU', 'OBT-010', 'Kapsul', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obat_keluars` +-- + +CREATE TABLE `obat_keluars` ( + `id` bigint(20) UNSIGNED NOT NULL, + `obat_masuk_id` bigint(20) UNSIGNED DEFAULT NULL, + `nama_obat` varchar(255) DEFAULT NULL, + `obat_id` bigint(20) UNSIGNED DEFAULT NULL, + `user_id` bigint(20) UNSIGNED NOT NULL, + `kode_batch` varchar(255) NOT NULL, + `barcode` varchar(255) DEFAULT NULL, + `jumlah` int(11) NOT NULL, + `harga` decimal(15,2) DEFAULT NULL, + `harga_total` decimal(15,2) DEFAULT NULL, + `tujuan_pemakaian` varchar(255) DEFAULT NULL, + `sumber_dana` varchar(255) DEFAULT NULL, + `tanggal_pengeluaran` date NOT NULL, + `tanggal_kadaluarsa` date NOT NULL, + `no_pengeluaran` varchar(255) DEFAULT NULL, + `nama_petugas` varchar(255) NOT NULL, + `nama_penerima` varchar(255) NOT NULL, + `tujuan` varchar(255) DEFAULT NULL, + `catatan` text DEFAULT NULL, + `status` enum('proses','selesai','dibatalkan') DEFAULT 'proses', + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obat_keluars` +-- + +INSERT INTO `obat_keluars` (`id`, `obat_masuk_id`, `nama_obat`, `obat_id`, `user_id`, `kode_batch`, `barcode`, `jumlah`, `harga`, `harga_total`, `tujuan_pemakaian`, `sumber_dana`, `tanggal_pengeluaran`, `tanggal_kadaluarsa`, `no_pengeluaran`, `nama_petugas`, `nama_penerima`, `tujuan`, `catatan`, `status`, `created_at`, `updated_at`) VALUES +(1, NULL, NULL, 1, 1, 'BCH-2024-001', NULL, 20, NULL, NULL, NULL, NULL, '2025-12-19', '2026-06-24', 'OUT-001', 'Admin', 'Apotek Sehat', 'Distribusi', NULL, 'selesai', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, NULL, NULL, 2, 1, 'BCH-2024-002', NULL, 15, NULL, NULL, NULL, NULL, '2025-12-20', '2026-02-07', 'OUT-002', 'Admin', 'Puskesmas Maju', 'Distribusi', NULL, 'selesai', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(3, NULL, NULL, 3, 1, 'BCH-2024-003', NULL, 30, NULL, NULL, NULL, NULL, '2025-12-21', '2026-12-24', 'OUT-003', 'Admin', 'RS Harapan', 'Distribusi', NULL, 'proses', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(4, NULL, NULL, 5, 1, 'BCH-2024-005', NULL, 50, NULL, NULL, NULL, NULL, '2025-12-22', '2026-03-09', 'OUT-004', 'Admin', 'Klinik Pratama', 'Distribusi', NULL, 'selesai', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(5, 7, 'Captopril 25mg', NULL, 1, 'BCH-2024-007', NULL, 2, NULL, NULL, 'RAWAT INAP', NULL, '2025-12-24', '2025-12-31', NULL, 'JARWO', 'siti', NULL, NULL, 'proses', '2025-12-24 05:00:17', '2025-12-24 05:00:17'), +(6, 3, 'Amoxicillin 500mg', NULL, 2, 'BCH-2024-003', NULL, 1, NULL, NULL, 'Resep: RSP-20260202-0001 - sakina', NULL, '2026-02-02', '2026-12-24', 'RSP-20260202-0001', 'Dokter', 'sakina', NULL, 'Otomatis dari resep RSP-20260202-0001', 'selesai', '2026-02-01 20:14:12', '2026-02-10 07:15:45'), +(7, 2, 'Ibuprofen 400mg', NULL, 2, 'BCH-2024-002', NULL, 1, NULL, NULL, 'Resep: RSP-20260203-0001 - elsa', NULL, '2026-02-03', '2026-02-07', 'RSP-20260203-0001', 'Dokter', 'elsa', NULL, 'Otomatis dari resep RSP-20260203-0001', 'selesai', '2026-02-02 20:12:25', '2026-02-02 20:12:25'), +(8, 6, 'Amlodipine 5mg', NULL, 2, 'BCH-2024-006', NULL, 1, NULL, NULL, 'Resep: RSP-20260210-0001 - a', NULL, '2026-02-10', '2026-08-24', 'RSP-20260210-0001', 'Dokter', 'a', NULL, 'Otomatis dari resep RSP-20260210-0001', 'selesai', '2026-02-10 07:20:40', '2026-02-10 08:00:24'), +(9, 6, 'Amlodipine 5mg', NULL, 2, 'BCH-2024-006', NULL, 1, NULL, NULL, 'Resep: RSP-20260215-0001 - Zeril', NULL, '2026-02-15', '2026-08-24', 'RSP-20260215-0001', 'Dokter', 'Zeril', NULL, 'Otomatis dari resep RSP-20260215-0001', 'selesai', '2026-02-15 08:11:11', '2026-02-15 08:11:58'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obat_masuks` +-- + +CREATE TABLE `obat_masuks` ( + `id` bigint(20) UNSIGNED NOT NULL, + `nama_obat` varchar(200) NOT NULL, + `kategori_id` bigint(20) UNSIGNED DEFAULT NULL, + `satuan_id` bigint(20) UNSIGNED DEFAULT NULL, + `sumber_dana` varchar(255) DEFAULT NULL, + `obat_id` bigint(20) UNSIGNED DEFAULT NULL, + `supplier_id` bigint(20) UNSIGNED DEFAULT NULL, + `user_id` bigint(20) UNSIGNED NOT NULL, + `kode_batch` varchar(255) NOT NULL, + `barcode` varchar(255) DEFAULT NULL, + `stok` int(11) NOT NULL, + `harga_beli` decimal(12,2) DEFAULT 0.00, + `harga_jual` decimal(12,2) DEFAULT 0.00, + `tanggal_penerimaan` date NOT NULL, + `tanggal_kadaluarsa` date NOT NULL, + `no_faktur` varchar(255) DEFAULT NULL, + `no_sbbk` varchar(255) DEFAULT NULL, + `catatan` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obat_masuks` +-- + +INSERT INTO `obat_masuks` (`id`, `nama_obat`, `kategori_id`, `satuan_id`, `sumber_dana`, `obat_id`, `supplier_id`, `user_id`, `kode_batch`, `barcode`, `stok`, `harga_beli`, `harga_jual`, `tanggal_penerimaan`, `tanggal_kadaluarsa`, `no_faktur`, `no_sbbk`, `catatan`, `created_at`, `updated_at`) VALUES +(1, 'Paracetamol 500mg', 1, 1, NULL, 1, 1, 1, 'BCH-2024-001', NULL, 100, 1500.00, 2500.00, '2025-11-24', '2026-06-24', 'FKT-001', NULL, NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, 'Ibuprofen 400mg', 1, 1, NULL, 2, 1, 1, 'BCH-2024-002', NULL, 79, 2000.00, 3500.00, '2025-11-29', '2026-02-07', 'FKT-002', NULL, NULL, '2025-12-24 04:52:26', '2026-02-02 20:12:25'), +(3, 'Amoxicillin 500mg', 2, 2, NULL, 3, 2, 1, 'BCH-2024-003', NULL, 149, 3000.00, 5000.00, '2025-12-04', '2026-12-24', 'FKT-003', NULL, NULL, '2025-12-24 04:52:26', '2026-02-01 20:14:12'), +(4, 'Ciprofloxacin 500mg', 2, 1, NULL, 4, 2, 1, 'BCH-2024-004', NULL, 60, 4000.00, 6500.00, '2025-12-09', '2026-01-13', 'FKT-004', NULL, NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(5, 'Aspirin 100mg', 3, 1, NULL, 5, 3, 1, 'BCH-2024-005', NULL, 200, 1000.00, 1800.00, '2025-12-14', '2026-03-09', 'FKT-005', NULL, NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(6, 'Amlodipine 5mg', 4, 1, NULL, 6, 1, 1, 'BCH-2024-006', NULL, 118, 2500.00, 4000.00, '2025-12-19', '2026-08-24', 'FKT-006', NULL, NULL, '2025-12-24 04:52:26', '2026-02-15 08:11:11'), +(7, 'Captopril 25mg', 4, 1, NULL, 7, 2, 1, 'BCH-2024-007', NULL, 88, 1800.00, 3000.00, '2025-12-21', '2026-01-08', 'FKT-007', NULL, NULL, '2025-12-24 04:52:26', '2025-12-24 05:00:17'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `password_reset_tokens` +-- + +CREATE TABLE `password_reset_tokens` ( + `email` varchar(255) NOT NULL, + `token` varchar(255) NOT NULL, + `created_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `reseps` +-- + +CREATE TABLE `reseps` ( + `id` bigint(20) UNSIGNED NOT NULL, + `no_resep` varchar(255) NOT NULL, + `jenis_penjamin` enum('umum','BPJS Kes','BPJS Naker','Jamkesmas/KIS','R. Inap') NOT NULL DEFAULT 'umum', + `jenis_layanan` enum('BP','KIA','Gigi','UGD','Lainnya') NOT NULL DEFAULT 'BP', + `user_id` bigint(20) UNSIGNED NOT NULL, + `nama_dokter` varchar(255) DEFAULT NULL, + `no_sip` varchar(255) DEFAULT NULL, + `nama_pasien` varchar(255) NOT NULL, + `no_rm` varchar(255) DEFAULT NULL, + `umur_pasien` int(11) DEFAULT NULL, + `berat_badan` decimal(5,2) DEFAULT NULL, + `alamat_pasien` varchar(255) DEFAULT NULL, + `jenis_kelamin` enum('L','P') DEFAULT NULL, + `tanggal_resep` date NOT NULL, + `diagnosa` text DEFAULT NULL, + `catatan` text DEFAULT NULL, + `status` enum('proses','selesai','dibatalkan') DEFAULT 'proses', + `is_read` tinyint(1) NOT NULL DEFAULT 0, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `reseps` +-- + +INSERT INTO `reseps` (`id`, `no_resep`, `jenis_penjamin`, `jenis_layanan`, `user_id`, `nama_dokter`, `no_sip`, `nama_pasien`, `no_rm`, `umur_pasien`, `berat_badan`, `alamat_pasien`, `jenis_kelamin`, `tanggal_resep`, `diagnosa`, `catatan`, `status`, `is_read`, `created_at`, `updated_at`) VALUES +(1, 'RSP-20260202-0001', 'umum', 'BP', 2, NULL, NULL, 'sakina', NULL, 21, NULL, 'nganjuk', NULL, '2026-02-02', 'batuk', NULL, 'selesai', 1, '2026-02-01 20:14:12', '2026-02-10 07:15:45'), +(2, 'RSP-20260203-0001', 'umum', 'BP', 2, NULL, NULL, 'elsa', NULL, 21, NULL, 'nganjuk', NULL, '2026-02-03', 'batuk', NULL, 'selesai', 1, '2026-02-02 20:12:25', '2026-02-08 18:43:51'), +(3, 'RSP-20260210-0001', 'umum', 'BP', 2, 'Dokter', '1', 'a', '1', 22, 22.00, 'a', 'L', '2026-02-10', 'q', NULL, 'selesai', 1, '2026-02-10 07:20:40', '2026-02-13 08:24:48'), +(4, 'RSP-20260215-0001', 'umum', 'UGD', 2, 'Sumanto Spjp', '3121', 'Zeril', '030475', 3, 29.00, 'Jalan sumolawang', 'L', '2026-02-15', 'Cholera', 'asdasd', 'selesai', 1, '2026-02-15 08:11:11', '2026-02-15 08:11:58'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `resep_items` +-- + +CREATE TABLE `resep_items` ( + `id` bigint(20) UNSIGNED NOT NULL, + `resep_id` bigint(20) UNSIGNED NOT NULL, + `obat_masuk_id` bigint(20) UNSIGNED NOT NULL, + `nama_obat` varchar(255) NOT NULL, + `jumlah` int(11) NOT NULL, + `aturan_pakai` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `resep_items` +-- + +INSERT INTO `resep_items` (`id`, `resep_id`, `obat_masuk_id`, `nama_obat`, `jumlah`, `aturan_pakai`, `created_at`, `updated_at`) VALUES +(1, 1, 3, 'Amoxicillin 500mg', 1, '3x1', '2026-02-01 20:14:12', '2026-02-01 20:14:12'), +(2, 2, 2, 'Ibuprofen 400mg', 1, '3x1', '2026-02-02 20:12:25', '2026-02-02 20:12:25'), +(3, 3, 6, 'Amlodipine 5mg', 1, '2', '2026-02-10 07:20:40', '2026-02-10 07:20:40'), +(4, 4, 6, 'Amlodipine 5mg', 1, '2x1 hari', '2026-02-15 08:11:11', '2026-02-15 08:11:11'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `satuans` +-- + +CREATE TABLE `satuans` ( + `id` bigint(20) UNSIGNED NOT NULL, + `nama` varchar(255) NOT NULL, + `keterangan` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `satuans` +-- + +INSERT INTO `satuans` (`id`, `nama`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 'Tablet', 'Obat dalam bentuk tablet', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, 'Kapsul', 'Obat dalam bentuk kapsul', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(3, 'Botol', 'Obat cair dalam botol', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(4, 'Strip', 'Obat dalam kemasan strip', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(5, 'Box', 'Obat dalam kemasan box', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(6, 'Ampul', 'Obat injeksi dalam ampul', '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(7, 'Tube', 'Obat salep/krim dalam tube', '2025-12-24 04:52:26', '2025-12-24 04:52:26'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `sessions` +-- + +CREATE TABLE `sessions` ( + `id` varchar(255) NOT NULL, + `user_id` bigint(20) UNSIGNED DEFAULT NULL, + `ip_address` varchar(45) DEFAULT NULL, + `user_agent` text DEFAULT NULL, + `payload` longtext NOT NULL, + `last_activity` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `sessions` +-- + +INSERT INTO `sessions` (`id`, `user_id`, `ip_address`, `user_agent`, `payload`, `last_activity`) VALUES +('1g3NQU9PNE9FQ0rvC1N3ned4PHXx0sIEbrsLRxUn', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoidEhLbXp0ME5Jbm1scUMwWG0zMHNCbGRRanRlUXFiaWRKbHlpbTlFTSI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771901099), +('9bKoAehhblz7fX4QRAJWTPj1TNsbxcKlJvcixar6', 1, '103.47.132.3', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoieDJiOGlPR01YNEpnZzFHZFhuNHM4YUV3WFd4WGlVbklwYUtjNDJwVyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6Mjc6Imh0dHA6Ly9tZWRvcnkubXkuaWQvbGFwb3JhbiI7czo1OiJyb3V0ZSI7czoxMzoibGFwb3Jhbi5pbmRleCI7fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtpOjE7fQ==', 1771905550), +('g0n8avzVw75To8mj6PdeIGnazvxnzx9r54IVoK7A', 1, '114.5.109.115', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoibEh6andaYkF3NEpKb0JvVU1WMnJwVDNKN0VzN0wwUkRkaEw0aXUxMSI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6Mjg6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL3Byb2ZpbGUiO3M6NToicm91dGUiO3M6MTI6InByb2ZpbGUuZWRpdCI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtpOjE7fQ==', 1771901915), +('gVxifbxk3gC95ALrQNEyR7Fxm5Fb9IdL3i53oGMz', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoidDlxTmJyZHNSSnowaUpIbEZmMHdyZXBraUwwZlNuY2pCZmtKSzFWYiI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771903034), +('kEUk9HMFphkzhmnZGbrlh8NkfVADsqDKZi3WFHlu', NULL, '103.22.242.5', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'YTozOntzOjY6Il90b2tlbiI7czo0MDoiaXNWZE1jZjN0cmRxaW5yeGZmektkd0xKckczcDN0UU9xbVVIdlczSyI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MjY6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL2xvZ2luIjtzOjU6InJvdXRlIjtzOjU6ImxvZ2luIjt9czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771904688), +('kWxA6pifHGIShUmw171i7iaWZx83ufptkw94PZIV', NULL, '117.103.171.5', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoiSEg0ZHZIWnl0MHgxc1llNFRUQmRXck5DSnRNSG1CYW5yZ1l4aDVhViI7czozOiJ1cmwiO2E6MTp7czo4OiJpbnRlbmRlZCI7czoyNjoiaHR0cHM6Ly9tZWRvcnkubXkuaWQvcmVzZXAiO31zOjk6Il9wcmV2aW91cyI7YToyOntzOjM6InVybCI7czoyNjoiaHR0cHM6Ly9tZWRvcnkubXkuaWQvbG9naW4iO3M6NToicm91dGUiO3M6NToibG9naW4iO31zOjY6Il9mbGFzaCI7YToyOntzOjM6Im9sZCI7YTowOnt9czozOiJuZXciO2E6MDp7fX19', 1771904719), +('LzU9kxWAhGdDTbxY6ulTkB4xD6pwcyTnkEdGeeGs', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoiMjEzQ3pQZkxBWHR2b0ZIQjFPZUJCOFJxY3ppOFlHWExDbXAyRGU1diI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771903012), +('NJ602ailiCYWtPwJ733AAzUbiu0zjy2WP5JbKvDl', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoiVE12NGxLa2lHMlZkcE1tZWQ5WUlMaURuTUtMNlBVUUZBR3RIM3pYSyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771903823), +('oWu1f2obqftNFn8lAEQDPM01nHyOKiNPCNEr8D9b', NULL, '103.189.123.8', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'YTozOntzOjY6Il90b2tlbiI7czo0MDoiajZqeXJHZWgwMWpKcUR4ZFV1Z0JGc1N2cTNwQTZpRGsyOUdBZHBLUiI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MjY6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL2xvZ2luIjtzOjU6InJvdXRlIjtzOjU6ImxvZ2luIjt9czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771904666), +('pEaBL9XBz9yhVbVnj7QP9PYhncndRsDrskQld8sI', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoiM21WUXJ0ZlFjVDltRElpQWhONGVnZzFlWmtHNUlJcjNMOVN4bmZiQyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771901475), +('PH9UfXWkZDRb0kR0vgIQZMnbuIfCkRQ5gwBEgMeO', 2, '36.79.208.192', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoib3BRd1hNQzdkUmxyVjZQcHo4TjZmdTZyblhMSzF2cXBRb0ZFeVNGciI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6Mjg6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL3Byb2ZpbGUiO3M6NToicm91dGUiO3M6MTI6InByb2ZpbGUuZWRpdCI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtpOjI7fQ==', 1771902801), +('R3R6fptYdUCdWWSXRvG846vWByqjTwTfkJYImKNH', NULL, '175.45.186.196', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoicElydDFubzd0dWJxYUdPUVd3NjRZMXlXNDBnd3NNdlpsUWVWS1NqMSI7czozOiJ1cmwiO2E6MTp7czo4OiJpbnRlbmRlZCI7czozMDoiaHR0cHM6Ly9tZWRvcnkubXkuaWQvZGFzaGJvYXJkIjt9czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MjY6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL2xvZ2luIjtzOjU6InJvdXRlIjtzOjU6ImxvZ2luIjt9czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771904687), +('Se0qp3o9aoWVH67XLfUkeXil7couCQFq23zJF8HI', 1, '36.79.208.192', 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Mobile Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoiZWFoaXRQNkRmZE5XVkp5dTR5bGRlaHlUWWFSM1ZrZXJwb1VIREEwaiI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6Mjg6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL3Byb2ZpbGUiO3M6NToicm91dGUiO3M6MTI6InByb2ZpbGUuZWRpdCI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtpOjE7fQ==', 1771904090), +('SUhF1hfELv5CcNoSdCsxJRnHalfjROwRVE23C8Tr', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoiT1VjQ0t0N1d1TUdMWmJ1bENYSjFCTkNldUVvUER5Rm5Wbk9LQnhMdyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771903000), +('UzTpFQbyY333ql8ftUi3tbwwX0NNeX6IUx6lUuCM', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoibDN0dDlLczhXYkpjZ0t2MHlwV2UyQ25DZ2xNRFNyQjNrbkhQZVRGYyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771905580), +('yD6o4MW50WcfNU5c0UxkBBaBMu0hOKwoQJdPyNIT', NULL, '195.88.211.210', '', 'YToyOntzOjY6Il90b2tlbiI7czo0MDoiZEZaalJlU0VhVnBDSkM1bXRyckNFMFhCazdXWXF6cE1NY29vZEZWTCI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319fQ==', 1771901045), +('yQXyf3tedUwzU8s7pJXnRzjI3Q6GS3RpSrtx9VxP', 2, '114.10.41.74', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoicXBNaWZOa0VqaEVaR2dRTTNsR1FEMWdoc3ljSHJFMTQ5OGVlOERhMyI7czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6MjY6Imh0dHBzOi8vbWVkb3J5Lm15LmlkL3Jlc2VwIjtzOjU6InJvdXRlIjtzOjExOiJyZXNlcC5pbmRleCI7fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtpOjI7fQ==', 1771904714); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `suppliers` +-- + +CREATE TABLE `suppliers` ( + `id` bigint(20) UNSIGNED NOT NULL, + `nama` varchar(255) NOT NULL, + `alamat` text DEFAULT NULL, + `telepon` varchar(255) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `suppliers` +-- + +INSERT INTO `suppliers` (`id`, `nama`, `alamat`, `telepon`, `email`, `created_at`, `updated_at`) VALUES +(1, 'PT Kimia Farma', 'Jakarta Pusat', '021-1234567', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(2, 'PT Kalbe Farma', 'Jakarta Timur', '021-7654321', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'), +(3, 'PT Sanbe Farma', 'Bandung', '022-1234567', NULL, '2025-12-24 04:52:26', '2025-12-24 04:52:26'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `users` +-- + +CREATE TABLE `users` ( + `id` bigint(20) UNSIGNED NOT NULL, + `name` varchar(255) NOT NULL, + `nip` varchar(255) DEFAULT NULL, + `email` varchar(255) NOT NULL, + `phone` varchar(255) DEFAULT NULL, + `gender` varchar(255) DEFAULT NULL, + `address` text DEFAULT NULL, + `division` varchar(255) DEFAULT NULL, + `position` varchar(255) DEFAULT NULL, + `profile_photo` varchar(255) DEFAULT NULL, + `role` enum('dokter','apoteker') NOT NULL DEFAULT 'apoteker', + `email_verified_at` timestamp NULL DEFAULT NULL, + `password` varchar(255) NOT NULL, + `remember_token` varchar(100) DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `users` +-- + +INSERT INTO `users` (`id`, `name`, `nip`, `email`, `phone`, `gender`, `address`, `division`, `position`, `profile_photo`, `role`, `email_verified_at`, `password`, `remember_token`, `created_at`, `updated_at`) VALUES +(1, 'Admin', '0987644876590', 'admin@admin.com', '081333940999', NULL, 'RB konveksi, darungan, babadan', 'apoteker', '-', 'profile-photos/Yp1w8l0LBt5TDVRNRAcmjUXToe7Z01wzo5I2V4yj.png', 'apoteker', NULL, '$2y$12$ezU30KN/7LX0OIaEKB4gxuU.fO/wfMVWk/KCCbyN24xelql7A688i', 'eEkWkkHR5zTNqOh1YT4Rd0S0TUMuwxqIWG4KyEDKcn4ZOc1gU2Vtl0Me0yAo', '2025-12-24 04:52:26', '2026-02-23 09:40:39'), +(2, 'Dokter', NULL, 'dokter@dokter.com', NULL, NULL, NULL, NULL, NULL, 'profile-photos/frOqnRsuJM56tmZiqWOAfu7iqNSIKgHBr6XMFCMK.jpg', 'dokter', '2026-02-01 00:18:12', '$2y$12$8RaiDGJxOjydcah39s2uWO.OsLn.0MLOayk82E/pPpofsQiRcD/MG', NULL, '2026-02-01 00:18:12', '2026-02-23 09:30:25'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indeks untuk tabel `cache` +-- +ALTER TABLE `cache` + ADD PRIMARY KEY (`key`); + +-- +-- Indeks untuk tabel `cache_locks` +-- +ALTER TABLE `cache_locks` + ADD PRIMARY KEY (`key`); + +-- +-- Indeks untuk tabel `failed_jobs` +-- +ALTER TABLE `failed_jobs` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`); + +-- +-- Indeks untuk tabel `jobs` +-- +ALTER TABLE `jobs` + ADD PRIMARY KEY (`id`), + ADD KEY `jobs_queue_index` (`queue`); + +-- +-- Indeks untuk tabel `job_batches` +-- +ALTER TABLE `job_batches` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `kategoris` +-- +ALTER TABLE `kategoris` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `migrations` +-- +ALTER TABLE `migrations` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `obats` +-- +ALTER TABLE `obats` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `obats_kode_unique` (`kode`), + ADD KEY `obats_kategori_id_foreign` (`kategori_id`); + +-- +-- Indeks untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + ADD PRIMARY KEY (`id`), + ADD KEY `obat_keluars_obat_id_foreign` (`obat_id`), + ADD KEY `obat_keluars_user_id_foreign` (`user_id`), + ADD KEY `obat_keluars_obat_masuk_id_foreign` (`obat_masuk_id`); + +-- +-- Indeks untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + ADD PRIMARY KEY (`id`), + ADD KEY `obat_masuks_obat_id_foreign` (`obat_id`), + ADD KEY `obat_masuks_supplier_id_foreign` (`supplier_id`), + ADD KEY `obat_masuks_user_id_foreign` (`user_id`), + ADD KEY `obat_masuks_kategori_id_foreign` (`kategori_id`), + ADD KEY `obat_masuks_satuan_id_foreign` (`satuan_id`); + +-- +-- Indeks untuk tabel `password_reset_tokens` +-- +ALTER TABLE `password_reset_tokens` + ADD PRIMARY KEY (`email`); + +-- +-- Indeks untuk tabel `reseps` +-- +ALTER TABLE `reseps` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `reseps_no_resep_unique` (`no_resep`), + ADD KEY `reseps_user_id_foreign` (`user_id`); + +-- +-- Indeks untuk tabel `resep_items` +-- +ALTER TABLE `resep_items` + ADD PRIMARY KEY (`id`), + ADD KEY `resep_items_resep_id_foreign` (`resep_id`), + ADD KEY `resep_items_obat_masuk_id_foreign` (`obat_masuk_id`); + +-- +-- Indeks untuk tabel `satuans` +-- +ALTER TABLE `satuans` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `sessions` +-- +ALTER TABLE `sessions` + ADD PRIMARY KEY (`id`), + ADD KEY `sessions_user_id_index` (`user_id`), + ADD KEY `sessions_last_activity_index` (`last_activity`); + +-- +-- Indeks untuk tabel `suppliers` +-- +ALTER TABLE `suppliers` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `users` +-- +ALTER TABLE `users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `users_email_unique` (`email`); + +-- +-- AUTO_INCREMENT untuk tabel yang dibuang +-- + +-- +-- AUTO_INCREMENT untuk tabel `failed_jobs` +-- +ALTER TABLE `failed_jobs` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT untuk tabel `jobs` +-- +ALTER TABLE `jobs` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT untuk tabel `kategoris` +-- +ALTER TABLE `kategoris` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + +-- +-- AUTO_INCREMENT untuk tabel `migrations` +-- +ALTER TABLE `migrations` + MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23; + +-- +-- AUTO_INCREMENT untuk tabel `obats` +-- +ALTER TABLE `obats` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11; + +-- +-- AUTO_INCREMENT untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + +-- +-- AUTO_INCREMENT untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + +-- +-- AUTO_INCREMENT untuk tabel `reseps` +-- +ALTER TABLE `reseps` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; + +-- +-- AUTO_INCREMENT untuk tabel `resep_items` +-- +ALTER TABLE `resep_items` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; + +-- +-- AUTO_INCREMENT untuk tabel `satuans` +-- +ALTER TABLE `satuans` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8; + +-- +-- AUTO_INCREMENT untuk tabel `suppliers` +-- +ALTER TABLE `suppliers` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- AUTO_INCREMENT untuk tabel `users` +-- +ALTER TABLE `users` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + +-- +-- Ketidakleluasaan untuk tabel pelimpahan (Dumped Tables) +-- + +-- +-- Ketidakleluasaan untuk tabel `obats` +-- +ALTER TABLE `obats` + ADD CONSTRAINT `obats_kategori_id_foreign` FOREIGN KEY (`kategori_id`) REFERENCES `kategoris` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + ADD CONSTRAINT `obat_keluars_obat_id_foreign` FOREIGN KEY (`obat_id`) REFERENCES `obats` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `obat_keluars_obat_masuk_id_foreign` FOREIGN KEY (`obat_masuk_id`) REFERENCES `obat_masuks` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_keluars_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + ADD CONSTRAINT `obat_masuks_kategori_id_foreign` FOREIGN KEY (`kategori_id`) REFERENCES `kategoris` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_obat_id_foreign` FOREIGN KEY (`obat_id`) REFERENCES `obats` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `obat_masuks_satuan_id_foreign` FOREIGN KEY (`satuan_id`) REFERENCES `satuans` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_supplier_id_foreign` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `reseps` +-- +ALTER TABLE `reseps` + ADD CONSTRAINT `reseps_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `resep_items` +-- +ALTER TABLE `resep_items` + ADD CONSTRAINT `resep_items_obat_masuk_id_foreign` FOREIGN KEY (`obat_masuk_id`) REFERENCES `obat_masuks` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `resep_items_resep_id_foreign` FOREIGN KEY (`resep_id`) REFERENCES `reseps` (`id`) ON DELETE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/migration_error.txt b/migration_error.txt new file mode 100644 index 0000000..3d1f2ee --- /dev/null +++ b/migration_error.txt @@ -0,0 +1,5 @@ +๏ปฟ + INFO Running migrations. + + 2026_02_08_000003_update_obat_keluars_table 295.04ms DONE + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b2ad0a3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3428 @@ +{ + "name": "pencatatanobat", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/vite": "^4.0.0", + "alpinejs": "^3.4.2", + "autoprefixer": "^10.4.2", + "axios": "^1.11.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^2.0.0", + "postcss": "^8.4.31", + "tailwindcss": "^3.1.0", + "vite": "^7.0.7" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz", + "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/alpinejs": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.3.tgz", + "integrity": "sha512-fSI6F5213FdpMC4IWaup92KhuH3jBX0VVqajRJ6cOTCy1cL6888KyXdGO+seAAkn+g6fnrxBqQEx6gRpQ5EZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2ea7e1d --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "devDependencies": { + "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/vite": "^4.0.0", + "alpinejs": "^3.4.2", + "autoprefixer": "^10.4.2", + "axios": "^1.11.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^2.0.0", + "postcss": "^8.4.31", + "tailwindcss": "^3.1.0", + "vite": "^7.0.7" + } +} diff --git a/pencatatanobat2.sql b/pencatatanobat2.sql new file mode 100644 index 0000000..85a1d03 --- /dev/null +++ b/pencatatanobat2.sql @@ -0,0 +1,584 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.1 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost:3306 +-- Waktu pembuatan: 27 Des 2025 pada 02.22 +-- Versi server: 8.0.30 +-- Versi PHP: 8.3.17 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `pencatatanobat2` +-- + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `cache` +-- + +CREATE TABLE `cache` ( + `key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `expiration` int NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `cache_locks` +-- + +CREATE TABLE `cache_locks` ( + `key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `owner` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `expiration` int NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `failed_jobs` +-- + +CREATE TABLE `failed_jobs` ( + `id` bigint UNSIGNED NOT NULL, + `uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `connection` text COLLATE utf8mb4_unicode_ci NOT NULL, + `queue` text COLLATE utf8mb4_unicode_ci NOT NULL, + `payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `jobs` +-- + +CREATE TABLE `jobs` ( + `id` bigint UNSIGNED NOT NULL, + `queue` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `attempts` tinyint UNSIGNED NOT NULL, + `reserved_at` int UNSIGNED DEFAULT NULL, + `available_at` int UNSIGNED NOT NULL, + `created_at` int UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `job_batches` +-- + +CREATE TABLE `job_batches` ( + `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `total_jobs` int NOT NULL, + `pending_jobs` int NOT NULL, + `failed_jobs` int NOT NULL, + `failed_job_ids` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `options` mediumtext COLLATE utf8mb4_unicode_ci, + `cancelled_at` int DEFAULT NULL, + `created_at` int NOT NULL, + `finished_at` int DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `kategoris` +-- + +CREATE TABLE `kategoris` ( + `id` bigint UNSIGNED NOT NULL, + `nama` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `kategoris` +-- + +INSERT INTO `kategoris` (`id`, `nama`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 'Analgesik', 'Obat pereda nyeri', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(2, 'Antibiotik', 'Obat anti bakteri', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(3, 'Antipiretik', 'Obat penurun demam', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(4, 'Antihipertensi', 'Obat tekanan darah tinggi', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(5, 'Antidiabetik', 'Obat diabetes', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(6, 'Vitamin dan Suplemen', 'Suplemen kesehatan', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(7, 'Test', NULL, '2025-12-20 09:09:40', '2025-12-20 09:09:40'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `migrations` +-- + +CREATE TABLE `migrations` ( + `id` int UNSIGNED NOT NULL, + `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `batch` int NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `migrations` +-- + +INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES +(1, '0001_01_01_000000_create_users_table', 1), +(2, '0001_01_01_000001_create_cache_table', 1), +(3, '0001_01_01_000002_create_jobs_table', 1), +(4, '2024_01_01_000003_create_kategoris_table', 1), +(5, '2024_01_01_000004_create_suppliers_table', 1), +(6, '2024_01_01_000005_create_obats_table', 1), +(7, '2024_01_01_000006_create_obat_masuks_table', 1), +(8, '2024_01_01_000007_create_obat_keluars_table', 1), +(9, '2024_01_01_000008_create_satuans_table', 1), +(10, '2024_01_01_000009_add_fields_to_obat_masuks_table', 1), +(11, '2025_12_20_151320_add_nama_supplier_to_obat_masuks_table', 2), +(12, '2025_12_20_152837_update_obat_keluars_use_obat_masuk', 3), +(13, '2025_12_20_154137_add_tujuan_pemakaian_to_obat_keluars_table', 4), +(14, '2025_12_20_155815_make_obat_id_nullable_on_obat_keluars', 5); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obats` +-- + +CREATE TABLE `obats` ( + `id` bigint UNSIGNED NOT NULL, + `kategori_id` bigint UNSIGNED NOT NULL, + `nama` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `kode` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `satuan` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `deskripsi` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obats` +-- + +INSERT INTO `obats` (`id`, `kategori_id`, `nama`, `kode`, `satuan`, `deskripsi`, `created_at`, `updated_at`) VALUES +(1, 1, 'Paracetamol 500mg', 'OBT-001', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(2, 1, 'Ibuprofen 400mg', 'OBT-002', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(3, 2, 'Amoxicillin 500mg', 'OBT-003', 'Kapsul', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(4, 2, 'Ciprofloxacin 500mg', 'OBT-004', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(5, 3, 'Aspirin 100mg', 'OBT-005', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(6, 4, 'Amlodipine 5mg', 'OBT-006', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(7, 4, 'Captopril 25mg', 'OBT-007', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(8, 5, 'Metformin 500mg', 'OBT-008', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(9, 6, 'Vitamin C 1000mg', 'OBT-009', 'Tablet', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(10, 6, 'Vitamin D3 1000IU', 'OBT-010', 'Kapsul', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obat_keluars` +-- + +CREATE TABLE `obat_keluars` ( + `id` bigint UNSIGNED NOT NULL, + `obat_masuk_id` bigint UNSIGNED DEFAULT NULL, + `nama_obat` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `obat_id` bigint UNSIGNED DEFAULT NULL, + `user_id` bigint UNSIGNED NOT NULL, + `kode_batch` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `jumlah` int NOT NULL, + `tujuan_pemakaian` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `tanggal_pengeluaran` date NOT NULL, + `tanggal_kadaluarsa` date NOT NULL, + `no_pengeluaran` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `nama_petugas` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `nama_penerima` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `tujuan` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `catatan` text COLLATE utf8mb4_unicode_ci, + `status` enum('terkirim','proses','dibatalkan') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'proses', + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obat_keluars` +-- + +INSERT INTO `obat_keluars` (`id`, `obat_masuk_id`, `nama_obat`, `obat_id`, `user_id`, `kode_batch`, `jumlah`, `tujuan_pemakaian`, `tanggal_pengeluaran`, `tanggal_kadaluarsa`, `no_pengeluaran`, `nama_petugas`, `nama_penerima`, `tujuan`, `catatan`, `status`, `created_at`, `updated_at`) VALUES +(5, 8, 'Panadol', NULL, 1, 'TEST-01', 10, 'ok', '2025-12-20', '2027-12-27', '22', 'Admin', 'ok', NULL, 'ok', 'proses', '2025-12-20 09:00:16', '2025-12-20 09:00:16'), +(6, 2, 'Ibuprofen 400mg', NULL, 1, 'BCH-2024-002', 20, 'OK', '2025-12-20', '2025-12-27', '2', 'Admin', 'OK', NULL, 'OK', 'proses', '2025-12-20 09:11:56', '2025-12-20 09:11:56'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `obat_masuks` +-- + +CREATE TABLE `obat_masuks` ( + `id` bigint UNSIGNED NOT NULL, + `nama_obat` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL, + `kategori_id` bigint UNSIGNED DEFAULT NULL, + `satuan_id` bigint UNSIGNED DEFAULT NULL, + `nama_supplier` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `obat_id` bigint UNSIGNED DEFAULT NULL, + `supplier_id` bigint UNSIGNED DEFAULT NULL, + `user_id` bigint UNSIGNED NOT NULL, + `kode_batch` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `stok` int NOT NULL, + `harga_beli` decimal(12,2) NOT NULL, + `harga_jual` decimal(12,2) NOT NULL, + `tanggal_penerimaan` date NOT NULL, + `tanggal_kadaluarsa` date NOT NULL, + `no_faktur` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `catatan` text COLLATE utf8mb4_unicode_ci, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `obat_masuks` +-- + +INSERT INTO `obat_masuks` (`id`, `nama_obat`, `kategori_id`, `satuan_id`, `nama_supplier`, `obat_id`, `supplier_id`, `user_id`, `kode_batch`, `stok`, `harga_beli`, `harga_jual`, `tanggal_penerimaan`, `tanggal_kadaluarsa`, `no_faktur`, `catatan`, `created_at`, `updated_at`) VALUES +(1, 'Paracetamol 500mg', 1, 1, NULL, 1, 1, 1, 'BCH-2024-001', 100, 1500.00, 2500.00, '2025-11-20', '2026-06-20', 'FKT-001', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(2, 'Ibuprofen 400mg', 1, 1, NULL, 2, 1, 1, 'BCH-2024-002', 60, 2000.00, 3500.00, '2025-11-25', '2026-02-03', 'FKT-002', NULL, '2025-12-20 06:50:43', '2025-12-20 09:11:56'), +(3, 'Amoxicillin 500mg', 2, 2, NULL, 3, 2, 1, 'BCH-2024-003', 150, 3000.00, 5000.00, '2025-11-30', '2026-12-20', 'FKT-003', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(4, 'Ciprofloxacin 500mg', 2, 1, NULL, 4, 2, 1, 'BCH-2024-004', 60, 4000.00, 6500.00, '2025-12-05', '2026-01-09', 'FKT-004', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(5, 'Aspirin 100mg', 3, 1, NULL, 5, 3, 1, 'BCH-2024-005', 200, 1000.00, 1800.00, '2025-12-10', '2026-03-05', 'FKT-005', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(6, 'Amlodipine 5mg', 4, 1, NULL, 6, 1, 1, 'BCH-2024-006', 120, 2500.00, 4000.00, '2025-12-15', '2026-08-20', 'FKT-006', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(7, 'Captopril 25mg', 4, 1, NULL, 7, 2, 1, 'BCH-2024-007', 90, 1800.00, 3000.00, '2025-12-17', '2026-01-04', 'FKT-007', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(8, 'Panadol', 2, 2, 'PT G', NULL, NULL, 1, 'TEST-01', 90, 100.00, 200.00, '2025-12-20', '2025-12-23', '0I111', 'OKE', '2025-12-20 08:16:11', '2025-12-20 09:13:51'), +(9, 'Test', 7, 8, 'PT TEST', NULL, NULL, 1, 'TEST-02', 200, 100.00, 200.00, '2025-12-20', '2027-10-12', '0111', 'OK', '2025-12-20 09:10:53', '2025-12-20 09:10:53'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `password_reset_tokens` +-- + +CREATE TABLE `password_reset_tokens` ( + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `satuans` +-- + +CREATE TABLE `satuans` ( + `id` bigint UNSIGNED NOT NULL, + `nama` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `keterangan` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `satuans` +-- + +INSERT INTO `satuans` (`id`, `nama`, `keterangan`, `created_at`, `updated_at`) VALUES +(1, 'Tablet', 'Obat dalam bentuk tablet', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(2, 'Kapsul', 'Obat dalam bentuk kapsul', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(3, 'Botol', 'Obat cair dalam botol', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(4, 'Strip', 'Obat dalam kemasan strip', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(5, 'Box', 'Obat dalam kemasan box', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(6, 'Ampul', 'Obat injeksi dalam ampul', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(7, 'Tube', 'Obat salep/krim dalam tube', '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(8, 'Ml', NULL, '2025-12-20 09:09:53', '2025-12-20 09:09:53'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `sessions` +-- + +CREATE TABLE `sessions` ( + `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id` bigint UNSIGNED DEFAULT NULL, + `ip_address` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_agent` text COLLATE utf8mb4_unicode_ci, + `payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `last_activity` int NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `sessions` +-- + +INSERT INTO `sessions` (`id`, `user_id`, `ip_address`, `user_agent`, `payload`, `last_activity`) VALUES +('CWvMXd923eSL9kHHDSY1XxwZybkx8zMz4ubBafEv', 1, '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36', 'YTo0OntzOjY6Il90b2tlbiI7czo0MDoiRXVxQWswVGtrNnFoU1gxemZxWXprUkZCa3lDVVdhMXFRRGZBRjVXeSI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6NDA6Imh0dHA6Ly8xMjcuMC4wLjE6ODAwMC9vYmF0LWtlbHVhci81L2VkaXQiO3M6NToicm91dGUiO3M6MTY6Im9iYXQta2VsdWFyLmVkaXQiO31zOjY6Il9mbGFzaCI7YToyOntzOjM6Im9sZCI7YTowOnt9czozOiJuZXciO2E6MDp7fX1zOjUwOiJsb2dpbl93ZWJfNTliYTM2YWRkYzJiMmY5NDAxNTgwZjAxNGM3ZjU4ZWE0ZTMwOTg5ZCI7aToxO30=', 1766578721), +('XEWcPtymuSTqb64lDcTm8RMIywgdqREpTYsMlHZi', NULL, '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0', 'YTozOntzOjY6Il90b2tlbiI7czo0MDoiQzF0YnJNV0lQWXV0UkVVVU1yN1pyOTMxV3ZUSVVDUmV1T2ZzNFAzOCI7czo5OiJfcHJldmlvdXMiO2E6Mjp7czozOiJ1cmwiO3M6Mzc6Imh0dHA6Ly8xMjcuMC4wLjE6ODAwMC9mb3Jnb3QtcGFzc3dvcmQiO3M6NToicm91dGUiO3M6MTY6InBhc3N3b3JkLnJlcXVlc3QiO31zOjY6Il9mbGFzaCI7YToyOntzOjM6Im9sZCI7YTowOnt9czozOiJuZXciO2E6MDp7fX19', 1766572325); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `suppliers` +-- + +CREATE TABLE `suppliers` ( + `id` bigint UNSIGNED NOT NULL, + `nama` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `alamat` text COLLATE utf8mb4_unicode_ci, + `telepon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `suppliers` +-- + +INSERT INTO `suppliers` (`id`, `nama`, `alamat`, `telepon`, `email`, `created_at`, `updated_at`) VALUES +(1, 'PT Kimia Farma', 'Jakarta Pusat', '021-1234567', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(2, 'PT Kalbe Farma', 'Jakarta Timur', '021-7654321', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'), +(3, 'PT Sanbe Farma', 'Bandung', '022-1234567', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'); + +-- -------------------------------------------------------- + +-- +-- Struktur dari tabel `users` +-- + +CREATE TABLE `users` ( + `id` bigint UNSIGNED NOT NULL, + `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `email_verified_at` timestamp NULL DEFAULT NULL, + `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Dumping data untuk tabel `users` +-- + +INSERT INTO `users` (`id`, `name`, `email`, `email_verified_at`, `password`, `remember_token`, `created_at`, `updated_at`) VALUES +(1, 'Admin', 'admin@admin.com', NULL, '$2y$12$wQzt9xdtR3BSEgostxRbRuEfRuKgfiNIIyU3RYh13uFPD7KrEMgGi', NULL, '2025-12-20 06:50:43', '2025-12-20 06:50:43'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indeks untuk tabel `cache` +-- +ALTER TABLE `cache` + ADD PRIMARY KEY (`key`); + +-- +-- Indeks untuk tabel `cache_locks` +-- +ALTER TABLE `cache_locks` + ADD PRIMARY KEY (`key`); + +-- +-- Indeks untuk tabel `failed_jobs` +-- +ALTER TABLE `failed_jobs` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`); + +-- +-- Indeks untuk tabel `jobs` +-- +ALTER TABLE `jobs` + ADD PRIMARY KEY (`id`), + ADD KEY `jobs_queue_index` (`queue`); + +-- +-- Indeks untuk tabel `job_batches` +-- +ALTER TABLE `job_batches` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `kategoris` +-- +ALTER TABLE `kategoris` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `migrations` +-- +ALTER TABLE `migrations` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `obats` +-- +ALTER TABLE `obats` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `obats_kode_unique` (`kode`), + ADD KEY `obats_kategori_id_foreign` (`kategori_id`); + +-- +-- Indeks untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + ADD PRIMARY KEY (`id`), + ADD KEY `obat_keluars_obat_id_foreign` (`obat_id`), + ADD KEY `obat_keluars_user_id_foreign` (`user_id`), + ADD KEY `obat_keluars_obat_masuk_id_foreign` (`obat_masuk_id`); + +-- +-- Indeks untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + ADD PRIMARY KEY (`id`), + ADD KEY `obat_masuks_obat_id_foreign` (`obat_id`), + ADD KEY `obat_masuks_supplier_id_foreign` (`supplier_id`), + ADD KEY `obat_masuks_user_id_foreign` (`user_id`), + ADD KEY `obat_masuks_kategori_id_foreign` (`kategori_id`), + ADD KEY `obat_masuks_satuan_id_foreign` (`satuan_id`); + +-- +-- Indeks untuk tabel `password_reset_tokens` +-- +ALTER TABLE `password_reset_tokens` + ADD PRIMARY KEY (`email`); + +-- +-- Indeks untuk tabel `satuans` +-- +ALTER TABLE `satuans` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `sessions` +-- +ALTER TABLE `sessions` + ADD PRIMARY KEY (`id`), + ADD KEY `sessions_user_id_index` (`user_id`), + ADD KEY `sessions_last_activity_index` (`last_activity`); + +-- +-- Indeks untuk tabel `suppliers` +-- +ALTER TABLE `suppliers` + ADD PRIMARY KEY (`id`); + +-- +-- Indeks untuk tabel `users` +-- +ALTER TABLE `users` + ADD PRIMARY KEY (`id`), + ADD UNIQUE KEY `users_email_unique` (`email`); + +-- +-- AUTO_INCREMENT untuk tabel yang dibuang +-- + +-- +-- AUTO_INCREMENT untuk tabel `failed_jobs` +-- +ALTER TABLE `failed_jobs` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT untuk tabel `jobs` +-- +ALTER TABLE `jobs` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT; + +-- +-- AUTO_INCREMENT untuk tabel `kategoris` +-- +ALTER TABLE `kategoris` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8; + +-- +-- AUTO_INCREMENT untuk tabel `migrations` +-- +ALTER TABLE `migrations` + MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15; + +-- +-- AUTO_INCREMENT untuk tabel `obats` +-- +ALTER TABLE `obats` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11; + +-- +-- AUTO_INCREMENT untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + +-- +-- AUTO_INCREMENT untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + +-- +-- AUTO_INCREMENT untuk tabel `satuans` +-- +ALTER TABLE `satuans` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + +-- +-- AUTO_INCREMENT untuk tabel `suppliers` +-- +ALTER TABLE `suppliers` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + +-- +-- AUTO_INCREMENT untuk tabel `users` +-- +ALTER TABLE `users` + MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; + +-- +-- Ketidakleluasaan untuk tabel pelimpahan (Dumped Tables) +-- + +-- +-- Ketidakleluasaan untuk tabel `obats` +-- +ALTER TABLE `obats` + ADD CONSTRAINT `obats_kategori_id_foreign` FOREIGN KEY (`kategori_id`) REFERENCES `kategoris` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `obat_keluars` +-- +ALTER TABLE `obat_keluars` + ADD CONSTRAINT `obat_keluars_obat_id_foreign` FOREIGN KEY (`obat_id`) REFERENCES `obats` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `obat_keluars_obat_masuk_id_foreign` FOREIGN KEY (`obat_masuk_id`) REFERENCES `obat_masuks` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_keluars_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; + +-- +-- Ketidakleluasaan untuk tabel `obat_masuks` +-- +ALTER TABLE `obat_masuks` + ADD CONSTRAINT `obat_masuks_kategori_id_foreign` FOREIGN KEY (`kategori_id`) REFERENCES `kategoris` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_obat_id_foreign` FOREIGN KEY (`obat_id`) REFERENCES `obats` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `obat_masuks_satuan_id_foreign` FOREIGN KEY (`satuan_id`) REFERENCES `satuans` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_supplier_id_foreign` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + ADD CONSTRAINT `obat_masuks_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d703241 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,35 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + + + diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..49c0612 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b574a59 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/build (6).zip b/public/build (6).zip new file mode 100644 index 0000000..92f2612 Binary files /dev/null and b/public/build (6).zip differ diff --git a/public/build.zip b/public/build.zip new file mode 100644 index 0000000..dc0abfe Binary files /dev/null and b/public/build.zip differ diff --git a/public/clear-cache.php b/public/clear-cache.php new file mode 100644 index 0000000..9ce4e67 --- /dev/null +++ b/public/clear-cache.php @@ -0,0 +1,13 @@ +handleRequest(Request::capture()); diff --git a/public/meddata-logo.png b/public/meddata-logo.png new file mode 100644 index 0000000..038bbef Binary files /dev/null and b/public/meddata-logo.png differ diff --git a/public/meddata-logoai.png b/public/meddata-logoai.png new file mode 100644 index 0000000..d38a093 Binary files /dev/null and b/public/meddata-logoai.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/resources.zip b/resources.zip new file mode 100644 index 0000000..648aed8 Binary files /dev/null and b/resources.zip differ diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100644 index 0000000..87c5f2d --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +[x-cloak] { + display: none !important; +} diff --git a/resources/js/app.js b/resources/js/app.js new file mode 100644 index 0000000..a8093be --- /dev/null +++ b/resources/js/app.js @@ -0,0 +1,7 @@ +import './bootstrap'; + +import Alpine from 'alpinejs'; + +window.Alpine = Alpine; + +Alpine.start(); diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js new file mode 100644 index 0000000..5f1390b --- /dev/null +++ b/resources/js/bootstrap.js @@ -0,0 +1,4 @@ +import axios from 'axios'; +window.axios = axios; + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; diff --git a/resources/views (2).zip b/resources/views (2).zip new file mode 100644 index 0000000..a220dec Binary files /dev/null and b/resources/views (2).zip differ diff --git a/resources/views (3).zip b/resources/views (3).zip new file mode 100644 index 0000000..6fa74ba Binary files /dev/null and b/resources/views (3).zip differ diff --git a/resources/views.zip b/resources/views.zip new file mode 100644 index 0000000..ab89a09 Binary files /dev/null and b/resources/views.zip differ diff --git a/resources/views/auth/confirm-password.blade.php b/resources/views/auth/confirm-password.blade.php new file mode 100644 index 0000000..3d38186 --- /dev/null +++ b/resources/views/auth/confirm-password.blade.php @@ -0,0 +1,27 @@ + +
+ {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} +
+ +
+ @csrf + + +
+ + + + + +
+ +
+ + {{ __('Confirm') }} + +
+
+
diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php new file mode 100644 index 0000000..64989d5 --- /dev/null +++ b/resources/views/auth/forgot-password.blade.php @@ -0,0 +1,198 @@ + + + + + + + Lupa Password - {{ config('app.name', 'Pencatatan Obat') }} + + + + + + + + + + + +
+ +
+
+
+
+ +
+

Lupa Password

+

Masukkan email Anda dan kami akan mengirimkan link untuk mengatur ulang password.

+
+ + @if (session('status')) +
+ {{ session('status') }} +
+ @endif + + @if ($errors->any()) +
+ + @foreach ($errors->all() as $error) + {{ $error }} + @endforeach +
+ @endif + +
+ @csrf + +
+ + + +
+ + +
+ + +
+
+ + diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 0000000..870e622 --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,434 @@ + + + + + + + Login - {{ config('app.name', 'Pencatatan Obat') }} + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100644 index 0000000..a857242 --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,52 @@ + +
+ @csrf + + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + + +
+ + +
+ + + + + +
+ +
+ + {{ __('Already registered?') }} + + + + {{ __('Register') }} + +
+
+
diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php new file mode 100644 index 0000000..e9a6559 --- /dev/null +++ b/resources/views/auth/reset-password.blade.php @@ -0,0 +1,200 @@ + + + + + + + Reset Password - {{ config('app.name', 'Pencatatan Obat') }} + + + + + + + + +
+ +
+
+
+
+ +
+

Reset Password

+

Masukkan password baru Anda

+
+ + @if ($errors->any()) +
+ + @foreach ($errors->all() as $error) + {{ $error }} + @endforeach +
+ @endif + +
+ @csrf + + + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + + +
+ + +
+
+
+ + + + diff --git a/resources/views/auth/verify-email.blade.php b/resources/views/auth/verify-email.blade.php new file mode 100644 index 0000000..eaf811d --- /dev/null +++ b/resources/views/auth/verify-email.blade.php @@ -0,0 +1,31 @@ + +
+ {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} +
+ + @if (session('status') == 'verification-link-sent') +
+ {{ __('A new verification link has been sent to the email address you provided during registration.') }} +
+ @endif + +
+
+ @csrf + +
+ + {{ __('Resend Verification Email') }} + +
+
+ +
+ @csrf + + +
+
+
diff --git a/resources/views/components/application-logo.blade.php b/resources/views/components/application-logo.blade.php new file mode 100644 index 0000000..46579cf --- /dev/null +++ b/resources/views/components/application-logo.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/components/auth-session-status.blade.php b/resources/views/components/auth-session-status.blade.php new file mode 100644 index 0000000..c4bd6e2 --- /dev/null +++ b/resources/views/components/auth-session-status.blade.php @@ -0,0 +1,7 @@ +@props(['status']) + +@if ($status) +
merge(['class' => 'font-medium text-sm text-green-600']) }}> + {{ $status }} +
+@endif diff --git a/resources/views/components/badge.blade.php b/resources/views/components/badge.blade.php new file mode 100644 index 0000000..0574702 --- /dev/null +++ b/resources/views/components/badge.blade.php @@ -0,0 +1,24 @@ +@props(['type' => 'default']) + +@php + $classes = match($type) { + 'aman' => 'badge-aman', + 'tersedia' => 'badge-aman', + 'selesai' => 'badge-aman', + 'waspada' => 'badge-waspada', + 'proses' => 'badge-waspada', + 'awas' => 'badge-awas', + 'kritis' => 'badge-kritis', + 'habis' => 'badge-kritis', + 'dibatalkan' => 'badge-kritis', + 'danger' => 'badge-kritis', + 'terkirim' => 'badge-aman', + 'dokter' => 'badge-waspada', + 'apoteker' => 'badge-aman', + default => 'bg-gray-100 text-gray-700', + }; +@endphp + +merge(['class' => 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ' . $classes]) }}> + {{ $slot }} + diff --git a/resources/views/components/btn.blade.php b/resources/views/components/btn.blade.php new file mode 100644 index 0000000..c8024b9 --- /dev/null +++ b/resources/views/components/btn.blade.php @@ -0,0 +1,24 @@ +@props(['type' => 'primary', 'href' => null]) + +@php + $classes = match($type) { + 'primary' => 'bg-[#4A538F] text-white hover:bg-[#424B84] focus:ring-[#4A538F]', + 'secondary' => 'bg-[#E9EBF5] text-[#4A538F] hover:bg-[#d8dce8] focus:ring-[#4A538F]', + 'danger' => 'bg-[#C0392B] text-white hover:bg-[#a93226] focus:ring-[#C0392B]', + 'success' => 'bg-[#1F9254] text-white hover:bg-[#177a45] focus:ring-[#1F9254]', + 'warning' => 'bg-[#B78103] text-white hover:bg-[#9a6d03] focus:ring-[#B78103]', + default => 'bg-[#4A538F] text-white hover:bg-[#424B84] focus:ring-[#4A538F]', + }; + + $baseClasses = 'inline-flex items-center gap-2 px-5 py-2.5 rounded-lg font-medium transition-colors focus:ring-2 focus:ring-offset-2 text-sm'; +@endphp + +@if($href) + merge(['class' => $baseClasses . ' ' . $classes]) }}> + {{ $slot }} + +@else + +@endif diff --git a/resources/views/components/card.blade.php b/resources/views/components/card.blade.php new file mode 100644 index 0000000..771a645 --- /dev/null +++ b/resources/views/components/card.blade.php @@ -0,0 +1,5 @@ +@props(['class' => '']) + +
merge(['class' => 'card-custom rounded-xl shadow-sm w-full ' . $class]) }}> + {{ $slot }} +
diff --git a/resources/views/components/danger-button.blade.php b/resources/views/components/danger-button.blade.php new file mode 100644 index 0000000..d17d288 --- /dev/null +++ b/resources/views/components/danger-button.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/dropdown-link.blade.php b/resources/views/components/dropdown-link.blade.php new file mode 100644 index 0000000..e0f8ce1 --- /dev/null +++ b/resources/views/components/dropdown-link.blade.php @@ -0,0 +1 @@ +merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} diff --git a/resources/views/components/dropdown.blade.php b/resources/views/components/dropdown.blade.php new file mode 100644 index 0000000..a46f7c8 --- /dev/null +++ b/resources/views/components/dropdown.blade.php @@ -0,0 +1,35 @@ +@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white']) + +@php +$alignmentClasses = match ($align) { + 'left' => 'ltr:origin-top-left rtl:origin-top-right start-0', + 'top' => 'origin-top', + default => 'ltr:origin-top-right rtl:origin-top-left end-0', +}; + +$width = match ($width) { + '48' => 'w-48', + default => $width, +}; +@endphp + +
+
+ {{ $trigger }} +
+ + +
diff --git a/resources/views/components/form-input.blade.php b/resources/views/components/form-input.blade.php new file mode 100644 index 0000000..e7eb068 --- /dev/null +++ b/resources/views/components/form-input.blade.php @@ -0,0 +1,26 @@ +@props(['label' => '', 'name', 'type' => 'text', 'value' => '', 'placeholder' => '', 'required' => false, 'error' => null]) + +
+ @if($label) + + @endif + + merge(['class' => 'w-full px-4 py-2.5 border border-[#E5E7F2] rounded-lg focus:ring-2 focus:ring-[#4A538F] focus:border-[#4A538F] transition-colors bg-white text-[#2F347A] placeholder-[#7A7FAE] ' . ($error ? 'border-red-500' : '')]) }} + > + + @if($error) +

{{ $error }}

+ @endif +
diff --git a/resources/views/components/form-select.blade.php b/resources/views/components/form-select.blade.php new file mode 100644 index 0000000..3dafad3 --- /dev/null +++ b/resources/views/components/form-select.blade.php @@ -0,0 +1,34 @@ +@props(['label' => '', 'name', 'options' => [], 'value' => '', 'placeholder' => 'Pilih...', 'required' => false, 'error' => null]) + +
+ @if($label) + + @endif + + + + @if($error) +

{{ $error }}

+ @endif +
diff --git a/resources/views/components/form-textarea.blade.php b/resources/views/components/form-textarea.blade.php new file mode 100644 index 0000000..f0bbfa8 --- /dev/null +++ b/resources/views/components/form-textarea.blade.php @@ -0,0 +1,25 @@ +@props(['label' => '', 'name', 'value' => '', 'placeholder' => '', 'required' => false, 'rows' => 4, 'error' => null]) + +
+ @if($label) + + @endif + + + + @if($error) +

{{ $error }}

+ @endif +
diff --git a/resources/views/components/input-error.blade.php b/resources/views/components/input-error.blade.php new file mode 100644 index 0000000..9e6da21 --- /dev/null +++ b/resources/views/components/input-error.blade.php @@ -0,0 +1,9 @@ +@props(['messages']) + +@if ($messages) +
    merge(['class' => 'text-sm text-red-600 space-y-1']) }}> + @foreach ((array) $messages as $message) +
  • {{ $message }}
  • + @endforeach +
+@endif diff --git a/resources/views/components/input-label.blade.php b/resources/views/components/input-label.blade.php new file mode 100644 index 0000000..1cc65e2 --- /dev/null +++ b/resources/views/components/input-label.blade.php @@ -0,0 +1,5 @@ +@props(['value']) + + diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php new file mode 100644 index 0000000..70704c1 --- /dev/null +++ b/resources/views/components/modal.blade.php @@ -0,0 +1,78 @@ +@props([ + 'name', + 'show' => false, + 'maxWidth' => '2xl' +]) + +@php +$maxWidth = [ + 'sm' => 'sm:max-w-sm', + 'md' => 'sm:max-w-md', + 'lg' => 'sm:max-w-lg', + 'xl' => 'sm:max-w-xl', + '2xl' => 'sm:max-w-2xl', +][$maxWidth]; +@endphp + +
+
+
+
+ +
+ {{ $slot }} +
+
diff --git a/resources/views/components/nav-link.blade.php b/resources/views/components/nav-link.blade.php new file mode 100644 index 0000000..5c101a2 --- /dev/null +++ b/resources/views/components/nav-link.blade.php @@ -0,0 +1,11 @@ +@props(['active']) + +@php +$classes = ($active ?? false) + ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' + : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; +@endphp + +merge(['class' => $classes]) }}> + {{ $slot }} + diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php new file mode 100644 index 0000000..63083ad --- /dev/null +++ b/resources/views/components/navbar.blade.php @@ -0,0 +1,28 @@ +@php + $notifikasiCount = \App\Models\ObatMasuk::where('tanggal_kadaluarsa', '<=', now()->addMonths(3)) + ->where('tanggal_kadaluarsa', '>', now()) + ->count(); +@endphp + +
+
+
+

@yield('title', 'Dashboard MedData')

+
+ +
+ + +
+
+
diff --git a/resources/views/components/notification-modal.blade.php b/resources/views/components/notification-modal.blade.php new file mode 100644 index 0000000..435e5d0 --- /dev/null +++ b/resources/views/components/notification-modal.blade.php @@ -0,0 +1,79 @@ +@php + $notifikasiKadaluarsa = \App\Models\ObatMasuk::where('tanggal_kadaluarsa', '<=', now()->addMonths(4)) + ->where('tanggal_kadaluarsa', '>', now()) // Exclude already expired (red) + ->orderBy('tanggal_kadaluarsa', 'asc') + ->get(); +@endphp + + diff --git a/resources/views/components/primary-button.blade.php b/resources/views/components/primary-button.blade.php new file mode 100644 index 0000000..d71f0b6 --- /dev/null +++ b/resources/views/components/primary-button.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/responsive-nav-link.blade.php b/resources/views/components/responsive-nav-link.blade.php new file mode 100644 index 0000000..43b91e7 --- /dev/null +++ b/resources/views/components/responsive-nav-link.blade.php @@ -0,0 +1,11 @@ +@props(['active']) + +@php +$classes = ($active ?? false) + ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' + : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; +@endphp + +merge(['class' => $classes]) }}> + {{ $slot }} + diff --git a/resources/views/components/secondary-button.blade.php b/resources/views/components/secondary-button.blade.php new file mode 100644 index 0000000..b32b69f --- /dev/null +++ b/resources/views/components/secondary-button.blade.php @@ -0,0 +1,3 @@ + diff --git a/resources/views/components/sidebar-link.blade.php b/resources/views/components/sidebar-link.blade.php new file mode 100644 index 0000000..1673f2f --- /dev/null +++ b/resources/views/components/sidebar-link.blade.php @@ -0,0 +1,10 @@ +@props(['active' => false]) + +merge([ + 'class' => 'flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 ' . + ($active + ? 'bg-white/20 text-white font-semibold' + : 'text-white/70 hover:bg-white/10 hover:text-white') +]) }}> + {{ $slot }} + diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php new file mode 100644 index 0000000..a2efa75 --- /dev/null +++ b/resources/views/components/sidebar.blade.php @@ -0,0 +1,128 @@ + diff --git a/resources/views/components/stat-card.blade.php b/resources/views/components/stat-card.blade.php new file mode 100644 index 0000000..0244940 --- /dev/null +++ b/resources/views/components/stat-card.blade.php @@ -0,0 +1,25 @@ +@props(['label', 'value', 'color' => 'indigo', 'iconBg' => null]) + +@php + $colorMap = [ + 'indigo' => ['bg' => 'bg-[#E9EBF5]', 'text' => 'text-[#4A538F]'], + 'green' => ['bg' => 'bg-[#C9F7E3]', 'text' => 'text-[#1F9254]'], + 'blue' => ['bg' => 'bg-[#E0F2FE]', 'text' => 'text-[#0369A1]'], + 'red' => ['bg' => 'bg-[#FFD6D6]', 'text' => 'text-[#C0392B]'], + ]; + $colors = $colorMap[$color] ?? $colorMap['indigo']; +@endphp + +
+
+
+
+ {{ $icon ?? '' }} +
+
+
+

{{ $label }}

+

{{ $value }}

+
+
+
diff --git a/resources/views/components/text-input.blade.php b/resources/views/components/text-input.blade.php new file mode 100644 index 0000000..da1b12d --- /dev/null +++ b/resources/views/components/text-input.blade.php @@ -0,0 +1,3 @@ +@props(['disabled' => false]) + +merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}> diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php new file mode 100644 index 0000000..66028f2 --- /dev/null +++ b/resources/views/dashboard.blade.php @@ -0,0 +1,17 @@ + + +

+ {{ __('Dashboard') }} +

+
+ +
+
+
+
+ {{ __("You're logged in!") }} +
+
+
+
+
diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php new file mode 100644 index 0000000..a23bfa4 --- /dev/null +++ b/resources/views/dashboard/index.blade.php @@ -0,0 +1,204 @@ + + @section('title', 'Dashboard MedData') + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+

Tren Obat Keluar Masuk {{ $selectedDate->translatedFormat('F Y') }}

+
+ + +
+
+
+ +
+
+
+ + + +
+

Obat Kadaluarsa / Mendekati Kadaluarsa (โ‰ค 120 Hari)

+
+
+ + + + + + + + + + + + + @forelse($obatKadaluarsa as $item) + @php + $sisaHari = (int) now()->diffInDays($item->tanggal_kadaluarsa, false); + if ($sisaHari <= 0) { + $status = 'danger'; + $statusLabel = 'Kadaluarsa'; + } elseif ($sisaHari <= 30) { + $status = 'awas'; + $statusLabel = 'Awas'; + } else { + $status = 'waspada'; + $statusLabel = 'Waspada'; + } + @endphp + + + + + + + + + @empty + + + + @endforelse + +
Nama ObatKode BatchStokTanggal KadaluarsaSisa HariStatus
+
{{ $item->nama_obat ?? 'N/A' }}
+
{{ $item->kode_batch }}{{ $item->stok }}{{ $item->tanggal_kadaluarsa->format('d M Y') }} + {{ $sisaHari <= 0 ? $sisaHari . ' hari (lewat)' : $sisaHari . ' hari' }} + + {{ $statusLabel }} +
+ + + + Tidak ada obat yang kadaluarsa atau mendekati kadaluarsa +
+
+
+
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/kadaluarsa/index.blade.php b/resources/views/kadaluarsa/index.blade.php new file mode 100644 index 0000000..810f4b2 --- /dev/null +++ b/resources/views/kadaluarsa/index.blade.php @@ -0,0 +1,95 @@ + + @section('title', 'Obat Kadaluarsa') + +
+ +
+
+

Daftar Obat Kadaluarsa

+

Menampilkan obat yang sudah kadaluarsa atau mendekati kadaluarsa (โ‰ค 120 hari)

+
+
+ + + +
+
+ + +
+ +
+
+ + + +
+

Daftar Obat Kadaluarsa

+
+
+ + + + + + + + + + + + + + @forelse($obatKadaluarsa as $item) + @php + $sisaHari = (int) now()->diffInDays($item->tanggal_kadaluarsa, false); + if ($sisaHari <= 0) { + $status = 'danger'; + $statusLabel = 'Kadaluarsa'; + } elseif ($sisaHari <= 30) { + $status = 'awas'; + $statusLabel = 'Awas'; + } else { + $status = 'waspada'; + $statusLabel = 'Waspada'; + } + @endphp + + + + + + + + + + @empty + + + + @endforelse + +
Nama ObatKode BatchKategoriStokTanggal KadaluarsaSisa HariStatus
{{ $item->nama_obat ?? 'N/A' }}{{ $item->kode_batch }}{{ $item->kategori->nama ?? 'N/A' }}{{ $item->stok }}{{ $item->tanggal_kadaluarsa->format('d M Y') }} + {{ $sisaHari <= 0 ? $sisaHari . ' hari (lewat)' : $sisaHari . ' hari' }} + + {{ $statusLabel }} +
+ + + + Tidak ada obat yang kadaluarsa atau mendekati kadaluarsa +
+
+
+ {{ $obatKadaluarsa->withQueryString()->links() }} +
+
+
+
diff --git a/resources/views/kategori/create.blade.php b/resources/views/kategori/create.blade.php new file mode 100644 index 0000000..2780859 --- /dev/null +++ b/resources/views/kategori/create.blade.php @@ -0,0 +1,32 @@ + + @section('title', 'Tambah Kategori') + +
+ + + +
+ @csrf + + + +
+ Batal + Simpan +
+ +
+
+
diff --git a/resources/views/kategori/edit.blade.php b/resources/views/kategori/edit.blade.php new file mode 100644 index 0000000..00293ee --- /dev/null +++ b/resources/views/kategori/edit.blade.php @@ -0,0 +1,33 @@ + + @section('title', 'Edit Kategori') + +
+ + + +
+ @csrf + @method('PUT') + + + +
+ Batal + Simpan Perubahan +
+ +
+
+
diff --git a/resources/views/kategori/index.blade.php b/resources/views/kategori/index.blade.php new file mode 100644 index 0000000..6a130a3 --- /dev/null +++ b/resources/views/kategori/index.blade.php @@ -0,0 +1,67 @@ + + @section('title', 'Kategori Obat') + +
+ +
+

Kategori Obat

+ + + + + Tambah Kategori + +
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + +
+

Daftar Kategori

+
+
+ + + + + + + + + + + @forelse($kategoris as $index => $kategori) + + + + + + + @empty + + + + @endforelse + +
NoNama KategoriKeteranganAksi
{{ $kategoris->firstItem() + $index }}{{ $kategori->nama }}{{ $kategori->keterangan ?? '-' }} +
+ Edit +
+ @csrf + @method('DELETE') + +
+
+
Belum ada data kategori
+
+
+ {{ $kategoris->links() }} +
+
+
+
diff --git a/resources/views/laporan/index.blade.php b/resources/views/laporan/index.blade.php new file mode 100644 index 0000000..e359cb3 --- /dev/null +++ b/resources/views/laporan/index.blade.php @@ -0,0 +1,98 @@ + + @section('title', 'Laporan') + +
+ + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ + + + + + +
+

{{ request('jenis', 'masuk') == 'masuk' ? 'Laporan Obat Masuk' : 'Laporan Obat Keluar' }}

+
+
+ + + + + + + + + + + @forelse($data as $item) + + @if(request('jenis', 'masuk') == 'masuk') + + + + + @else + + + + + @endif + + @empty + + + + @endforelse + +
TanggalNama ObatKode BatchJumlah
{{ $item->tanggal_penerimaan->format('d M Y') }}{{ $item->nama_obat ?? 'N/A' }}{{ $item->kode_batch }}{{ $item->stok }}{{ $item->tanggal_pengeluaran->format('d M Y') }}{{ $item->nama_obat ?? 'N/A' }}{{ $item->kode_batch }}{{ $item->jumlah }}
+ Tidak ada data untuk periode yang dipilih +
+
+
+ {{ $data->withQueryString()->links() }} +
+
+
+
diff --git a/resources/views/laporan/pdf.blade.php b/resources/views/laporan/pdf.blade.php new file mode 100644 index 0000000..bd6e382 --- /dev/null +++ b/resources/views/laporan/pdf.blade.php @@ -0,0 +1,128 @@ + + + + + {{ $title }} + + + +
+

{{ $title }}

+

Periode: {{ \Carbon\Carbon::parse($tanggalMulai)->format('d M Y') }} - {{ \Carbon\Carbon::parse($tanggalAkhir)->format('d M Y') }}

+
+ + + @if($jenis === 'masuk') + + + + + + + + + + @php $grandTotal = 0; @endphp + @foreach($data as $index => $item) + @php $grandTotal += $item->total_jumlah; @endphp + + + + + + + @endforeach + + + + + + @else + + + + + + + + + + @php $grandTotal = 0; @endphp + @foreach($data as $index => $item) + @php $grandTotal += $item->total_jumlah; @endphp + + + + + + + @endforeach + + + + + + @endif +
NoNama ObatSatuanTotal Jumlah Masuk
{{ $index + 1 }}{{ $item->nama_obat ?? 'N/A' }}{{ $item->satuan ?? '-' }}{{ number_format($item->total_jumlah, 0, ',', '.') }}
TOTAL{{ number_format($grandTotal, 0, ',', '.') }}
NoNama ObatSatuanTotal Jumlah Keluar
{{ $index + 1 }}{{ $item->nama_obat ?? 'N/A' }}{{ $item->satuan ?? '-' }}{{ number_format($item->total_jumlah, 0, ',', '.') }}
TOTAL{{ number_format($grandTotal, 0, ',', '.') }}
+ + + + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..78d8b25 --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,100 @@ + + + + + + + @yield('title', 'Dashboard') - MedData + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + + + + + + + +
+ + + + +
+ {{ $slot }} +
+
+ + + + + @stack('scripts') + + diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/guest.blade.php new file mode 100644 index 0000000..11feb47 --- /dev/null +++ b/resources/views/layouts/guest.blade.php @@ -0,0 +1,30 @@ + + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + +
+
+ + + +
+ +
+ {{ $slot }} +
+
+ + diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php new file mode 100644 index 0000000..c2d3a65 --- /dev/null +++ b/resources/views/layouts/navigation.blade.php @@ -0,0 +1,100 @@ + diff --git a/resources/views/obat-keluar/create.blade.php b/resources/views/obat-keluar/create.blade.php new file mode 100644 index 0000000..9590a25 --- /dev/null +++ b/resources/views/obat-keluar/create.blade.php @@ -0,0 +1,409 @@ + + @section('title', 'Tambah Obat Keluar') + +
+ + + +
+

Tambah Obat Keluar

+
+ + +
+ +
+
+ 1 + + Informasi Obat + +
+
+ + +
+
+ 2 + + Data Administratif + +
+
+
+ +
+ @csrf + + +
+ +
+
+ + + +

+ Info: Kategori dan satuan obat akan otomatis terisi berdasarkan data obat yang dipilih. Stok akan otomatis berkurang sesuai jumlah yang dikeluarkan. +

+
+ +
+ + + +

Informasi Obat

+ + +
+ + +

+ @error('obat_masuk_id') +

{{ $message }}

+ @enderror +
+ + +
+ +
+ + +

+ @error('kode_batch') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('barcode') +

{{ $message }}

+ @enderror +
+ + +
+ + +

+ @error('tujuan_pemakaian') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('sumber_dana') +

{{ $message }}

+ @enderror +
+
+
+ + + +

Jumlah, Harga, dan Tanggal

+ +
+ +
+ + +

+ @error('jumlah') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('harga') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('harga_total') +

{{ $message }}

+ @enderror +
+ + +
+ + +

+ @error('tanggal_pengeluaran') +

{{ $message }}

+ @enderror +
+ + +
+ + +

+ @error('tanggal_kadaluarsa') +

{{ $message }}

+ @enderror +
+
+
+ + +
+ Batal + +
+
+ + +
+ +

Data Administratif

+ +
+ +
+ + + @error('no_pengeluaran') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('nama_penerima') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('nama_petugas') +

{{ $message }}

+ @enderror +
+ + + +
+ + +
+ + + @error('catatan') +

{{ $message }}

+ @enderror +
+
+ + +
+ + +
+
+
+
+
diff --git a/resources/views/obat-keluar/edit.blade.php b/resources/views/obat-keluar/edit.blade.php new file mode 100644 index 0000000..6b91fe4 --- /dev/null +++ b/resources/views/obat-keluar/edit.blade.php @@ -0,0 +1,217 @@ + + @section('title', 'Edit Obat Keluar') + +
+ + + +
+

Edit Obat Keluar

+
+ +
+ @csrf + @method('PUT') + + + +

Pilih Obat

+ +
+ + + @foreach($obats as $obat) + + @endforeach + + + + + + + + + + + + + + + + + +
+
+ + + +

Informasi Pengeluaran

+ +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + +
+ Batal + Simpan Perubahan +
+
+
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/obat-keluar/index.blade.php b/resources/views/obat-keluar/index.blade.php new file mode 100644 index 0000000..4f6028f --- /dev/null +++ b/resources/views/obat-keluar/index.blade.php @@ -0,0 +1,263 @@ + + @section('title', 'Data Obat Keluar') + +
+ +
+
+

+
+ @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + + + + + Tambah Obat + + @endif +
+ + + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + + +
+
+ +
+
+ +
+
+ +
+
+ + + Reset + +
+
+
+ + + +
+

Trend Obat Keluar

+
+ + + + + + + +
+
+ @if(count($pieLabels) > 0) +
+
+ +
+
+ @foreach($pieLabels as $index => $label) + @php + $percentage = $totalUsage > 0 ? round(($pieData[$index] / $totalUsage) * 100, 1) : 0; + $colors = ['#1E3A8A', '#3B82F6', '#7C3AED', '#06B6D4', '#6366F1']; + @endphp +
+
+ + {{ $label }} +
+ {{ number_format($pieData[$index]) }} ({{ $percentage }}%) +
+ @endforeach +
+
+ Total Penggunaan + {{ number_format($totalUsage) }} unit +
+
+
+
+ @else +
+
+ + + +

Tidak ada data obat keluar

+

pada bulan {{ $selectedDate->translatedFormat('F Y') }}

+
+
+ @endif +
+ + + +
+

Daftar Obat Keluar

+
+
+ + + + + + + + + + + + + + @forelse($obatKeluars as $item) + + + + + + + + + + @empty + + + + @endforelse + +
TanggalNama ObatKode BatchJumlahPenerimaStatusAksi
+ {{ $item->tanggal_pengeluaran->format('d M Y') }} + +
{{ $item->nama_obat ?? 'N/A' }}
+
{{ $item->kode_batch }}{{ $item->jumlah }}{{ $item->nama_penerima }} + + {{ ucfirst($item->status) }} + + +
+ +
+ Detail + @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + Edit +
+ @csrf + @method('DELETE') + +
+ @endif +
+
+
+ Belum ada data obat keluar +
+
+
+ {{ $obatKeluars->withQueryString()->links() }} +
+
+
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/obat-keluar/show.blade.php b/resources/views/obat-keluar/show.blade.php new file mode 100644 index 0000000..cd97224 --- /dev/null +++ b/resources/views/obat-keluar/show.blade.php @@ -0,0 +1,127 @@ + + @section('title', 'Detail Obat Keluar') + + @push('styles') + + @endpush + +
+ + + +
+

Detail Obat Keluar

+
+ + + + + Print + + @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + + + + + Edit + + @endif + Kembali +
+
+ +
+ + +

Informasi Obat

+
+
+
Nama Obat
+
{{ $obatKeluar->nama_obat ?? 'N/A' }}
+
+
+
Kode Batch
+
{{ $obatKeluar->kode_batch }}
+
+
+
Jumlah
+
{{ $obatKeluar->jumlah }}
+
+
+
Tanggal Kadaluarsa
+
{{ $obatKeluar->tanggal_kadaluarsa->format('d M Y') }}
+
+
+
+ + + +

Informasi Pengeluaran

+
+
+
No. Pengeluaran
+
{{ $obatKeluar->no_pengeluaran ?? '-' }}
+
+
+
Tanggal Pengeluaran
+
{{ $obatKeluar->tanggal_pengeluaran->format('d M Y') }}
+
+
+
Nama Petugas
+
{{ $obatKeluar->nama_petugas }}
+
+
+
Status
+
+ {{ ucfirst($obatKeluar->status) }} +
+
+
+
+ + + +

Penerima

+
+
+
Nama Penerima
+
{{ $obatKeluar->nama_penerima }}
+
+
+
Tujuan
+
{{ $obatKeluar->tujuan ?? '-' }}
+
+
+
+ + + +

Catatan

+

{{ $obatKeluar->catatan ?? 'Tidak ada catatan' }}

+
+
+
+
diff --git a/resources/views/obat-masuk/create.blade.php b/resources/views/obat-masuk/create.blade.php new file mode 100644 index 0000000..15f8afb --- /dev/null +++ b/resources/views/obat-masuk/create.blade.php @@ -0,0 +1,164 @@ + + @section('title', 'Tambah Obat Masuk') + +
+ + + +
+

Tambah Obat Masuk

+
+ +
+ @csrf + + + +

Informasi Obat

+ +
+ + + + + + + @foreach($kategoris as $kategori) + + @endforeach + + + + + @foreach($satuans as $satuan) + + @endforeach + +
+
+ + + +

Sumber Dana & Stok

+ +
+ + + + + + + + + +
+
+ + + +

Tanggal

+ +
+ + + +
+
+ + + + + + + +
+ Batal + Simpan +
+
+
+
diff --git a/resources/views/obat-masuk/edit.blade.php b/resources/views/obat-masuk/edit.blade.php new file mode 100644 index 0000000..4a9f8fa --- /dev/null +++ b/resources/views/obat-masuk/edit.blade.php @@ -0,0 +1,165 @@ + + @section('title', 'Edit Obat Masuk') + +
+ + + +
+

Edit Obat Masuk

+
+ +
+ @csrf + @method('PUT') + + + +

Informasi Obat

+ +
+ + + + + + + @foreach($kategoris as $kategori) + + @endforeach + + + + + @foreach($satuans as $satuan) + + @endforeach + +
+
+ + + +

Sumber Dana & Stok

+ +
+ + + + + + + + + +
+
+ + + +

Tanggal

+ +
+ + + +
+
+ + + + + + + +
+ Batal + Simpan Perubahan +
+
+
+
diff --git a/resources/views/obat-masuk/index.blade.php b/resources/views/obat-masuk/index.blade.php new file mode 100644 index 0000000..1382e51 --- /dev/null +++ b/resources/views/obat-masuk/index.blade.php @@ -0,0 +1,268 @@ + + @section('title', 'Data Obat Masuk') + +
+ +
+
+

+
+ @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + + + + + Tambah Obat + + @endif +
+ + + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + + +
+
+ +
+
+ +
+
+ +
+
+ + + Reset + +
+
+
+ + + +
+

Trend Obat Masuk

+
+ + + + + + + +
+
+ @if(count($pieLabels) > 0) +
+
+ +
+
+ @foreach($pieLabels as $index => $label) + @php + $percentage = $totalStock > 0 ? round(($pieData[$index] / $totalStock) * 100, 1) : 0; + $colors = ['#1E3A8A', '#3B82F6', '#7C3AED', '#06B6D4', '#6366F1']; + @endphp +
+
+ + {{ $label }} +
+ {{ number_format($pieData[$index]) }} ({{ $percentage }}%) +
+ @endforeach +
+
+ Total Stok Masuk + {{ number_format($totalStock) }} unit +
+
+
+
+ @else +
+
+ + + +

Tidak ada data obat masuk

+

pada bulan {{ $selectedDate->translatedFormat('F Y') }}

+
+
+ @endif +
+ + + +
+

Daftar Obat Masuk

+
+
+ + + + + + + + + + + + + + + @forelse($obatMasuks as $item) + + + + + + + + + + + @empty + + + + @endforelse + +
TanggalNama ObatKode BatchKategoriSatuanStokStatusAksi
+ {{ $item->tanggal_penerimaan->format('d M Y') }} + +
{{ $item->nama_obat }}
+
{{ $item->kode_batch }} + {{ $item->kategori->nama ?? 'N/A' }} + + {{ $item->satuan->nama ?? 'N/A' }} + {{ $item->stok }} + + {{ $item->stok > 0 ? 'Tersedia' : 'Habis' }} + + +
+ +
+ Detail + @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + Edit +
+ @csrf + @method('DELETE') + +
+ @endif +
+
+
+ Belum ada data obat masuk +
+
+
+ {{ $obatMasuks->withQueryString()->links() }} +
+
+
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/obat-masuk/show.blade.php b/resources/views/obat-masuk/show.blade.php new file mode 100644 index 0000000..2f1701b --- /dev/null +++ b/resources/views/obat-masuk/show.blade.php @@ -0,0 +1,147 @@ + + @section('title', 'Detail Obat Masuk') + + @push('styles') + + @endpush + +
+ + + +
+

Detail Obat Masuk

+
+ + + + + Print + + @if(auth()->user()->isApoteker() || auth()->user()->isSuperAdmin()) + + + + + Edit + + @endif + Kembali +
+
+ +
+ + +

Informasi Obat

+
+
+
Nama Obat
+
{{ $obatMasuk->nama_obat }}
+
+
+
Kode Batch
+
{{ $obatMasuk->kode_batch }}
+
+ +
+
Kategori
+
{{ $obatMasuk->kategori->nama ?? 'N/A' }}
+
+
+
Satuan
+
{{ $obatMasuk->satuan->nama ?? 'N/A' }}
+
+ +
+
+ + + +

Sumber Dana & Stok

+
+
+
Sumber Dana
+
{{ $obatMasuk->sumber_dana ?? '-' }}
+
+
+
Barcode Obat
+
{{ $obatMasuk->barcode ?? '-' }}
+
+
+
Stok
+
{{ $obatMasuk->stok }}
+
+
+
No. Faktur
+
{{ $obatMasuk->no_faktur ?? '-' }}
+
+
+
No. SBBK
+
{{ $obatMasuk->no_sbbk ?? '-' }}
+
+
+
+ + + + + +

Tanggal

+
+
+
Tanggal Penerimaan
+
{{ $obatMasuk->tanggal_penerimaan->format('d M Y') }}
+
+
+
Tanggal Kadaluarsa
+
{{ $obatMasuk->tanggal_kadaluarsa->format('d M Y') }}
+
+
+
Sisa Hari
+
+ {{ $obatMasuk->sisa_hari }} hari +
+
+
+
Status
+
+ + {{ $obatMasuk->status_kadaluarsa == 'kritis' ? 'Awas' : ($obatMasuk->status_kadaluarsa == 'waspada' ? 'Waspada' : 'Aman') }} + +
+
+
+
+ + + +

Catatan

+

{{ $obatMasuk->catatan ?? 'Tidak ada catatan' }}

+
+
+
+
diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php new file mode 100644 index 0000000..2a02d0b --- /dev/null +++ b/resources/views/profile/edit.blade.php @@ -0,0 +1,481 @@ + + @section('title', 'Profil Saya') + +

+ Profil Saya +

+
+ + + +
+ + {{-- Tab Navigation --}} +
+ + +
+ + {{-- Tab: Informasi Profil --}} +
+ + {{-- Hidden form untuk verifikasi email --}} +
+ @csrf +
+ +
+ + {{-- Card Foto Profil (kiri) --}} +
+ {{-- Header --}} +
+
+ @if(auth()->user()->profile_photo) + Foto profil + @else + + + + @endif +
+

{{ auth()->user()->name }}

+ @if(auth()->user()->position) + + {{ auth()->user()->position }} + + @endif +
+ + {{-- Form Upload Foto --}} +
+

Foto Profil

+
+ @csrf + @method('patch') + + +

JPG atau PNG, maks. 2MB

+ + + + + {{-- Info singkat --}} +
+
+

Email

+

{{ auth()->user()->email ?? '-' }}

+
+
+

NIP

+

{{ auth()->user()->nip ?? '-' }}

+
+
+

Divisi

+

{{ auth()->user()->division ?? '-' }}

+
+
+
+
+ + {{-- Card Data Profil (kanan) --}} +
+ {{-- Header --}} +
+
+

Data Profil

+

Informasi lengkap akun Anda

+
+ +
+ + {{-- View Mode --}} +
+
+
+

NIP

+

{{ auth()->user()->nip ?? '-' }}

+
+
+

Nama Lengkap

+

{{ auth()->user()->name ?? '-' }}

+
+
+

Email

+

{{ auth()->user()->email ?? '-' }}

+
+
+

No. Telepon

+

{{ auth()->user()->phone ?? '-' }}

+
+
+

Jenis Kelamin

+

{{ auth()->user()->gender ?? '-' }}

+
+
+

Divisi

+

{{ auth()->user()->division ?? '-' }}

+
+
+

Jabatan

+

{{ auth()->user()->position ?? '-' }}

+
+
+

Alamat

+

{{ auth()->user()->address ?? '-' }}

+
+
+
+ + {{-- Edit Mode (hidden by default) --}} + +
+ +
+
{{-- /panel-info --}} + + {{-- Tab: Ubah Password --}} + {{-- /panel-password --}} + +
+ + +
diff --git a/resources/views/profile/partials/delete-user-form.blade.php b/resources/views/profile/partials/delete-user-form.blade.php new file mode 100644 index 0000000..edeeb4a --- /dev/null +++ b/resources/views/profile/partials/delete-user-form.blade.php @@ -0,0 +1,55 @@ +
+
+

+ {{ __('Delete Account') }} +

+ +

+ {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }} +

+
+ + {{ __('Delete Account') }} + + +
+ @csrf + @method('delete') + +

+ {{ __('Are you sure you want to delete your account?') }} +

+ +

+ {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} +

+ +
+ + + + + +
+ +
+ + {{ __('Cancel') }} + + + + {{ __('Delete Account') }} + +
+
+
+
diff --git a/resources/views/profile/partials/update-password-form.blade.php b/resources/views/profile/partials/update-password-form.blade.php new file mode 100644 index 0000000..eaca1ac --- /dev/null +++ b/resources/views/profile/partials/update-password-form.blade.php @@ -0,0 +1,48 @@ +
+
+

+ {{ __('Update Password') }} +

+ +

+ {{ __('Ensure your account is using a long, random password to stay secure.') }} +

+
+ +
+ @csrf + @method('put') + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ {{ __('Save') }} + + @if (session('status') === 'password-updated') +

{{ __('Saved.') }}

+ @endif +
+
+
diff --git a/resources/views/profile/partials/update-profile-information-form.blade.php b/resources/views/profile/partials/update-profile-information-form.blade.php new file mode 100644 index 0000000..3bdba47 --- /dev/null +++ b/resources/views/profile/partials/update-profile-information-form.blade.php @@ -0,0 +1,113 @@ +
+
+

+ Informasi Profil +

+ +

+ Perbarui data profil akun Anda. +

+
+ +
+ @csrf +
+ +
+ @csrf + @method('patch') + +
+ + @if($user->profile_photo) + Foto profil + @endif + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + + @if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail()) +
+

+ {{ __('Your email address is unverified.') }} + + +

+ + @if (session('status') === 'verification-link-sent') +

+ {{ __('A new verification link has been sent to your email address.') }} +

+ @endif +
+ @endif +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ {{ __('Save') }} + + @if (session('status') === 'profile-updated') +

{{ __('Saved.') }}

+ @endif +
+
+
diff --git a/resources/views/resep.zip b/resources/views/resep.zip new file mode 100644 index 0000000..03921c7 Binary files /dev/null and b/resources/views/resep.zip differ diff --git a/resources/views/resep/create.blade.php b/resources/views/resep/create.blade.php new file mode 100644 index 0000000..09c1af6 --- /dev/null +++ b/resources/views/resep/create.blade.php @@ -0,0 +1,275 @@ + + @section('title', 'Tambah Resep') + +
+ + + +
+

Tambah Resep Baru

+
+ + + @if($errors->any()) +
+ @foreach($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+ @endif + +
+ @csrf + +
+ +
+ + +

Data Resep

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + +

Data Pasien

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + +
+ +
+

Daftar Obat

+ +
+ +
+ +
+ +
+ Klik "Tambah Obat" untuk menambahkan obat ke resep +
+
+ + +
+ Batal + +
+
+
+
+
+ + @push('scripts') + + @endpush +
diff --git a/resources/views/resep/edit.blade.php b/resources/views/resep/edit.blade.php new file mode 100644 index 0000000..e8c2ef4 --- /dev/null +++ b/resources/views/resep/edit.blade.php @@ -0,0 +1,297 @@ + + @section('title', 'Edit Resep') + +
+ + + +
+

Edit Resep: {{ $resep->no_resep }}

+
+ + + @if($errors->any()) +
+ @foreach($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+ @endif + +
+ @csrf + @method('PUT') + +
+ +
+ + +

Data Resep

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + + +

Data Pasien

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + +
+ +
+

Daftar Obat

+ +
+ +
+ +
+ +
+ Klik "Tambah Obat" untuk menambahkan obat ke resep +
+
+ + +
+ Batal + +
+
+
+
+
+ + + @push('scripts') + @php + $itemsJson = $resep->items->map(function($item) { + return [ + 'obat_masuk_id' => (string) $item->obat_masuk_id, + 'jumlah' => $item->jumlah, + 'aturan_pakai' => $item->aturan_pakai ?? '', + 'stok' => $item->obatMasuk->stok ?? null + ]; + })->values()->toArray(); + @endphp + + @endpush +
diff --git a/resources/views/resep/index.blade.php b/resources/views/resep/index.blade.php new file mode 100644 index 0000000..a6e2315 --- /dev/null +++ b/resources/views/resep/index.blade.php @@ -0,0 +1,261 @@ + + @section('title', 'Data Resep') + +
+ +
+
+

Data Resep

+
+ @if(auth()->user()->canManageResep()) + + + + + Tambah Resep + + @endif +
+ + + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + + @if($errors->any()) +
+ @foreach($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+ @endif + + + +
+
+ +
+
+ +
+
+ + + Reset + +
+
+
+ + + +
+

Daftar Resep

+
+
+ + + + + + + + + + + + + + @forelse($reseps as $resep) + + + + + + + + + + @empty + + + + @endforelse + +
No. ResepTanggalNama PasienDokterJumlah ObatStatusAksi
+
{{ $resep->no_resep }}
+
+ {{ $resep->tanggal_resep->format('d M Y') }} + +
+ {{ $resep->nama_pasien }} + @if(auth()->user()->isApoteker() && !$resep->is_read) + + @endif +
+ @if($resep->umur_pasien) +
{{ $resep->umur_pasien }} tahun
+ @endif +
+ {{ $resep->user->name ?? 'N/A' }} + + {{ $resep->items->count() }} item + + + {{ ucfirst($resep->status) }} + + +
+ +
+ Detail + Print + @if(auth()->user()->isApoteker() && $resep->status !== 'selesai') + + @endif + @if(auth()->user()->canManageResep() && $resep->status !== 'selesai') + Edit +
+ @csrf + @method('DELETE') + +
+ @endif +
+
+
+ Belum ada data resep +
+
+
+ {{ $reseps->withQueryString()->links() }} +
+
+
+ + {{-- Global Status Modal (Pure JavaScript - No Alpine.js) --}} + @if(auth()->user()->isApoteker()) + + + + @endif +
diff --git a/resources/views/resep/print.blade.php b/resources/views/resep/print.blade.php new file mode 100644 index 0000000..4d56673 --- /dev/null +++ b/resources/views/resep/print.blade.php @@ -0,0 +1,344 @@ + + + + + + Resep - {{ $resep->no_resep }} + + + + + +
+ +
+

UPT PUSKESMAS DOKO

+

Jl. Sersan Toyib No.02 Suru Doko, Blitar

+

Telp: (0342) 692118 | Email: pkm.doko@blitarkab.go.id

+
+
+ + +
+
+

No. Resep: {{ $resep->no_resep }}

+
+
+

Tanggal: {{ $resep->tanggal_resep->format('d F Y') }}

+
+
+ + +
+

Data Pasien

+ + + + + + @if($resep->umur_pasien) + + + + + @endif + @if($resep->alamat_pasien) + + + + + @endif +
Nama: {{ $resep->nama_pasien }}
Umur: {{ $resep->umur_pasien }} tahun
Alamat: {{ $resep->alamat_pasien }}
+
+ + + @if($resep->diagnosa) +
+

Diagnosa

+

{{ $resep->diagnosa }}

+
+ @endif + + +
+

Obat yang Diresepkan

+ + + + + + + + + + + @foreach($resep->items as $index => $item) + + + + + + + @endforeach + +
NoNama ObatJumlahAturan Pakai
{{ $index + 1 }} + {{ $item->nama_obat }} + {{ $item->jumlah }} {{ $item->obatMasuk->satuan->nama ?? '' }} + {{ $item->aturan_pakai ?? '-' }} +
+
+ + + @if($resep->catatan) +
+

Catatan

+

{{ $resep->catatan }}

+
+ @endif + + + +
+ + diff --git a/resources/views/resep/show.blade.php b/resources/views/resep/show.blade.php new file mode 100644 index 0000000..a2efc57 --- /dev/null +++ b/resources/views/resep/show.blade.php @@ -0,0 +1,207 @@ + + @section('title', 'Detail Resep') + +
+ + + +
+

Detail Resep: {{ $resep->no_resep }}

+
+ + + + + Print + + @if(auth()->user()->canManageResep() && $resep->status !== 'selesai') + + + + + Edit + + @endif + Kembali +
+
+ + {{-- Flash messages --}} + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + +
+ +
+ + +

Informasi Resep

+
+
+
No. Resep
+
{{ $resep->no_resep }}
+
+
+
Tanggal
+
{{ $resep->tanggal_resep->format('d M Y') }}
+
+
+
Dokter
+
{{ $resep->nama_dokter ?? $resep->user->name ?? 'N/A' }}
+
+
+
No. SIP
+
{{ $resep->no_sip ?? '-' }}
+
+
+
Jenis Penjamin
+
{{ $resep->jenis_penjamin ?? '-' }}
+
+
+
Jenis Layanan
+
{{ $resep->jenis_layanan ?? '-' }}
+
+
+
Status
+
+ {{ ucfirst($resep->status) }} +
+
+
+
+ + + +

Data Pasien

+
+
+
No. RM
+
{{ $resep->no_rm ?? '-' }}
+
+
+
Nama
+
{{ $resep->nama_pasien }}
+
+
+
Jenis Kelamin
+
{{ $resep->jenis_kelamin == 'L' ? 'Laki-laki' : ($resep->jenis_kelamin == 'P' ? 'Perempuan' : '-') }}
+
+ @if($resep->umur_pasien) +
+
Umur
+
{{ $resep->umur_pasien }} tahun
+
+ @endif + @if($resep->berat_badan) +
+
Berat Badan
+
{{ $resep->berat_badan }} kg
+
+ @endif + @if($resep->alamat_pasien) +
+
Alamat
+
{{ $resep->alamat_pasien }}
+
+ @endif +
+
+ + + @if($resep->diagnosa || $resep->catatan) + +

Diagnosa & Catatan

+ @if($resep->diagnosa) +
+
Diagnosa
+
{{ $resep->diagnosa }}
+
+ @endif + @if($resep->catatan) +
+
Catatan
+
{{ $resep->catatan }}
+
+ @endif +
+ @endif +
+ + +
+ +
+

Daftar Obat yang Diresepkan

+
+
+ + + + + + + + + + + @forelse($resep->items as $index => $item) + + + + + + + @empty + + + + @endforelse + +
NoNama ObatJumlahAturan Pakai
+ {{ $index + 1 }} + +
{{ $item->nama_obat }}
+ @if($item->obatMasuk) +
Batch: {{ $item->obatMasuk->kode_batch }}
+ @endif +
+ {{ $item->jumlah }} {{ $item->obatMasuk->satuan->nama ?? '' }} + + {{ $item->aturan_pakai ?? '-' }} +
+ Tidak ada obat dalam resep ini +
+
+
+
+ Total Item + {{ $resep->items->count() }} obat +
+
+
+
+
+
+
diff --git a/resources/views/satuan/create.blade.php b/resources/views/satuan/create.blade.php new file mode 100644 index 0000000..092a0b0 --- /dev/null +++ b/resources/views/satuan/create.blade.php @@ -0,0 +1,32 @@ + + @section('title', 'Tambah Satuan') + +
+ + + +
+ @csrf + + + +
+ Batal + Simpan +
+ +
+
+
diff --git a/resources/views/satuan/edit.blade.php b/resources/views/satuan/edit.blade.php new file mode 100644 index 0000000..8c958fb --- /dev/null +++ b/resources/views/satuan/edit.blade.php @@ -0,0 +1,33 @@ + + @section('title', 'Edit Satuan') + +
+ + + +
+ @csrf + @method('PUT') + + + +
+ Batal + Simpan Perubahan +
+ +
+
+
diff --git a/resources/views/satuan/index.blade.php b/resources/views/satuan/index.blade.php new file mode 100644 index 0000000..6c754ed --- /dev/null +++ b/resources/views/satuan/index.blade.php @@ -0,0 +1,67 @@ + + @section('title', 'Satuan Obat') + +
+ +
+

Satuan Obat

+ + + + + Tambah Satuan + +
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + + +
+

Daftar Satuan

+
+
+ + + + + + + + + + + @forelse($satuans as $index => $satuan) + + + + + + + @empty + + + + @endforelse + +
NoNama SatuanKeteranganAksi
{{ $satuans->firstItem() + $index }}{{ $satuan->nama }}{{ $satuan->keterangan ?? '-' }} +
+ Edit +
+ @csrf + @method('DELETE') + +
+
+
Belum ada data satuan
+
+
+ {{ $satuans->links() }} +
+
+
+
diff --git a/resources/views/user-management/create.blade.php b/resources/views/user-management/create.blade.php new file mode 100644 index 0000000..bdb2b1a --- /dev/null +++ b/resources/views/user-management/create.blade.php @@ -0,0 +1,110 @@ + + @section('title', 'Tambah Pengguna') + +
+ + + +

Tambah Pengguna Baru

+ +
+ @csrf + + +

Informasi Akun

+ +
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Password

+ +
+ + + +
+
+ +
+ Batal + Simpan +
+
+
+
diff --git a/resources/views/user-management/edit.blade.php b/resources/views/user-management/edit.blade.php new file mode 100644 index 0000000..58e74d8 --- /dev/null +++ b/resources/views/user-management/edit.blade.php @@ -0,0 +1,110 @@ + + @section('title', 'Edit Pengguna') + +
+ + + +

Edit Pengguna: {{ $user->name }}

+ +
+ @csrf + @method('PUT') + + +

Informasi Akun

+ +
+ + + + + + + + + + + + + + + + + + + +
+
+ + +

Password

+

Kosongkan jika tidak ingin mengubah password.

+ +
+ + + +
+
+ +
+ Batal + Simpan Perubahan +
+
+
+
diff --git a/resources/views/user-management/index.blade.php b/resources/views/user-management/index.blade.php new file mode 100644 index 0000000..97cb0fa --- /dev/null +++ b/resources/views/user-management/index.blade.php @@ -0,0 +1,132 @@ + + @section('title', 'Kelola Pengguna') + +
+ +
+
+

Kelola Pengguna

+

Kelola akun dokter dan apoteker

+
+ + + + + Tambah Pengguna + +
+ + + +
+
+ + +
+
+ + +
+ +
+
+ + + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + + + +
+

Daftar Pengguna

+
+
+ + + + + + + + + + + + @forelse($users as $user) + + + + + + + + @empty + + + + @endforelse + +
NamaEmailNIPRoleAksi
+
+ @if($user->profile_photo) + Foto + @else +
+ {{ substr($user->name, 0, 1) }} +
+ @endif + {{ $user->name }} +
+
{{ $user->email }}{{ $user->nip ?? '-' }} + {{ ucfirst($user->role) }} + +
+ + + + + Edit + +
+ @csrf + @method('DELETE') + +
+
+
+ + + + Tidak ada pengguna ditemukan +
+
+
+ {{ $users->withQueryString()->links() }} +
+
+
+
diff --git a/resources/views/vendor/mail/html/button.blade.php b/resources/views/vendor/mail/html/button.blade.php new file mode 100644 index 0000000..050e969 --- /dev/null +++ b/resources/views/vendor/mail/html/button.blade.php @@ -0,0 +1,24 @@ +@props([ + 'url', + 'color' => 'primary', + 'align' => 'center', +]) + + + + + diff --git a/resources/views/vendor/mail/html/footer.blade.php b/resources/views/vendor/mail/html/footer.blade.php new file mode 100644 index 0000000..3ff41f8 --- /dev/null +++ b/resources/views/vendor/mail/html/footer.blade.php @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php new file mode 100644 index 0000000..1ee36ba --- /dev/null +++ b/resources/views/vendor/mail/html/header.blade.php @@ -0,0 +1,9 @@ +@props(['url']) + + + + +MedData + + + \ No newline at end of file diff --git a/resources/views/vendor/mail/html/layout.blade.php b/resources/views/vendor/mail/html/layout.blade.php new file mode 100644 index 0000000..0fa6b82 --- /dev/null +++ b/resources/views/vendor/mail/html/layout.blade.php @@ -0,0 +1,58 @@ + + + +{{ config('app.name') }} + + + + + +{!! $head ?? '' !!} + + + + + + + + + + diff --git a/resources/views/vendor/mail/html/message.blade.php b/resources/views/vendor/mail/html/message.blade.php new file mode 100644 index 0000000..a16bace --- /dev/null +++ b/resources/views/vendor/mail/html/message.blade.php @@ -0,0 +1,27 @@ + +{{-- Header --}} + + +{{ config('app.name') }} + + + +{{-- Body --}} +{!! $slot !!} + +{{-- Subcopy --}} +@isset($subcopy) + + +{!! $subcopy !!} + + +@endisset + +{{-- Footer --}} + + +ยฉ {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }} + + + diff --git a/resources/views/vendor/mail/html/panel.blade.php b/resources/views/vendor/mail/html/panel.blade.php new file mode 100644 index 0000000..2975a60 --- /dev/null +++ b/resources/views/vendor/mail/html/panel.blade.php @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/views/vendor/mail/html/subcopy.blade.php b/resources/views/vendor/mail/html/subcopy.blade.php new file mode 100644 index 0000000..790ce6c --- /dev/null +++ b/resources/views/vendor/mail/html/subcopy.blade.php @@ -0,0 +1,7 @@ + + + + + diff --git a/resources/views/vendor/mail/html/table.blade.php b/resources/views/vendor/mail/html/table.blade.php new file mode 100644 index 0000000..a5f3348 --- /dev/null +++ b/resources/views/vendor/mail/html/table.blade.php @@ -0,0 +1,3 @@ +
+{{ Illuminate\Mail\Markdown::parse($slot) }} +
diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css new file mode 100644 index 0000000..80465b2 --- /dev/null +++ b/resources/views/vendor/mail/html/themes/default.css @@ -0,0 +1,297 @@ +/* Base */ + +body, +body *:not(html):not(style):not(br):not(tr):not(code) { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + position: relative; +} + +body { + -webkit-text-size-adjust: none; + background-color: #ffffff; + color: #52525b; + height: 100%; + line-height: 1.4; + margin: 0; + padding: 0; + width: 100% !important; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #18181b; +} + +a img { + border: none; +} + +/* Typography */ + +h1 { + color: #18181b; + font-size: 18px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h2 { + font-size: 16px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h3 { + font-size: 14px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +p { + font-size: 16px; + line-height: 1.5em; + margin-top: 0; + text-align: left; +} + +p.sub { + font-size: 12px; +} + +img { + max-width: 100%; +} + +/* Layout */ + +.wrapper { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #fafafa; + margin: 0; + padding: 0; + width: 100%; +} + +.content { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: #18181b; + font-size: 19px; + font-weight: bold; + text-decoration: none; +} + +/* Logo */ + +.logo { + height: 75px; + margin-top: 15px; + margin-bottom: 10px; + max-height: 75px; + width: 75px; +} + +/* Body */ + +.body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #fafafa; + border-bottom: 1px solid #fafafa; + border-top: 1px solid #fafafa; + margin: 0; + padding: 0; + width: 100%; +} + +.inner-body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + background-color: #ffffff; + border-color: #e4e4e7; + border-radius: 4px; + border-width: 1px; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); + margin: 0 auto; + padding: 0; + width: 570px; +} + +.inner-body a { + word-break: break-all; +} + +/* Subcopy */ + +.subcopy { + border-top: 1px solid #e4e4e7; + margin-top: 25px; + padding-top: 25px; +} + +.subcopy p { + font-size: 14px; +} + +/* Footer */ + +.footer { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + margin: 0 auto; + padding: 0; + text-align: center; + width: 570px; +} + +.footer p { + color: #a1a1aa; + font-size: 12px; + text-align: center; +} + +.footer a { + color: #a1a1aa; + text-decoration: underline; +} + +/* Tables */ + +.table table { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + width: 100%; +} + +.table th { + border-bottom: 1px solid #e4e4e7; + margin: 0; + padding-bottom: 8px; +} + +.table td { + color: #52525b; + font-size: 15px; + line-height: 18px; + margin: 0; + padding: 10px 0; +} + +.content-cell { + max-width: 100vw; + padding: 32px; +} + +/* Buttons */ + +.action { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + padding: 0; + text-align: center; + width: 100%; + float: unset; +} + +.button { + -webkit-text-size-adjust: none; + border-radius: 4px; + color: #fff; + display: inline-block; + overflow: hidden; + text-decoration: none; +} + +.button-blue, +.button-primary { + background-color: #18181b; + border-bottom: 8px solid #18181b; + border-left: 18px solid #18181b; + border-right: 18px solid #18181b; + border-top: 8px solid #18181b; +} + +.button-green, +.button-success { + background-color: #16a34a; + border-bottom: 8px solid #16a34a; + border-left: 18px solid #16a34a; + border-right: 18px solid #16a34a; + border-top: 8px solid #16a34a; +} + +.button-red, +.button-error { + background-color: #dc2626; + border-bottom: 8px solid #dc2626; + border-left: 18px solid #dc2626; + border-right: 18px solid #dc2626; + border-top: 8px solid #dc2626; +} + +/* Panels */ + +.panel { + border-left: #18181b solid 4px; + margin: 21px 0; +} + +.panel-content { + background-color: #fafafa; + color: #52525b; + padding: 16px; +} + +.panel-content p { + color: #52525b; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + margin-bottom: 0; + padding-bottom: 0; +} + +/* Utilities */ + +.break-all { + word-break: break-all; +} diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php new file mode 100644 index 0000000..97444eb --- /dev/null +++ b/resources/views/vendor/mail/text/button.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/footer.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php new file mode 100644 index 0000000..97444eb --- /dev/null +++ b/resources/views/vendor/mail/text/header.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php new file mode 100644 index 0000000..ec58e83 --- /dev/null +++ b/resources/views/vendor/mail/text/layout.blade.php @@ -0,0 +1,9 @@ +{!! strip_tags($header ?? '') !!} + +{!! strip_tags($slot) !!} +@isset($subcopy) + +{!! strip_tags($subcopy) !!} +@endisset + +{!! strip_tags($footer ?? '') !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php new file mode 100644 index 0000000..80bce21 --- /dev/null +++ b/resources/views/vendor/mail/text/message.blade.php @@ -0,0 +1,27 @@ + + {{-- Header --}} + + + {{ config('app.name') }} + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + ยฉ {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + + + diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/panel.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/subcopy.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/table.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 0000000..b7355d7 --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,277 @@ + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite(['resources/css/app.css', 'resources/js/app.js']) + @else + + @endif + + +
+ @if (Route::has('login')) + + @endif +
+
+
+
+

Let's get started

+

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

+ + +
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- Light Mode 12 SVG --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{-- Dark Mode 12 SVG --}} + +
+
+
+
+ + @if (Route::has('login')) + + @endif + + diff --git a/routes/auth.php b/routes/auth.php new file mode 100644 index 0000000..3926ecf --- /dev/null +++ b/routes/auth.php @@ -0,0 +1,59 @@ +group(function () { + Route::get('register', [RegisteredUserController::class, 'create']) + ->name('register'); + + Route::post('register', [RegisteredUserController::class, 'store']); + + Route::get('login', [AuthenticatedSessionController::class, 'create']) + ->name('login'); + + Route::post('login', [AuthenticatedSessionController::class, 'store']); + + Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) + ->name('password.request'); + + Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) + ->name('password.email'); + + Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) + ->name('password.reset'); + + Route::post('reset-password', [NewPasswordController::class, 'store']) + ->name('password.store'); +}); + +Route::middleware('auth')->group(function () { + Route::get('verify-email', EmailVerificationPromptController::class) + ->name('verification.notice'); + + Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) + ->middleware(['signed', 'throttle:6,1']) + ->name('verification.verify'); + + Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) + ->middleware('throttle:6,1') + ->name('verification.send'); + + Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) + ->name('password.confirm'); + + Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); + + Route::put('password', [PasswordController::class, 'update'])->name('password.update'); + + Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) + ->name('logout'); +}); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..3c9adf1 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..94e6220 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,74 @@ +route('login'); +}); + +Route::middleware(['auth'])->group(function () { + // Dashboard - All roles can access + Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); + + // Resep - All roles can view + Route::get('/resep', [ResepController::class, 'index'])->name('resep.index'); + Route::get('/resep/{resep}', [ResepController::class, 'show'])->name('resep.show'); + Route::get('/resep/{resep}/print', [ResepController::class, 'print'])->name('resep.print'); + + // Resep - Dokter only (create, edit, delete) + Route::middleware(['role:dokter'])->group(function () { + Route::get('/resep-create', [ResepController::class, 'create'])->name('resep.create'); + Route::post('/resep', [ResepController::class, 'store'])->name('resep.store'); + Route::get('/resep/{resep}/edit', [ResepController::class, 'edit'])->name('resep.edit'); + Route::put('/resep/{resep}', [ResepController::class, 'update'])->name('resep.update'); + Route::delete('/resep/{resep}', [ResepController::class, 'destroy'])->name('resep.destroy'); + }); + + // Kadaluarsa - All roles can access + Route::get('/kadaluarsa', [KadaluarsaController::class, 'index'])->name('kadaluarsa.index'); + + // Apoteker only routes + Route::middleware(['role:apoteker'])->group(function () { + // Resep - Update status by apoteker + Route::patch('/resep/{resep}/update-status', [ResepController::class, 'updateStatus'])->name('resep.update-status'); + }); + + // Obat Masuk, Obat Keluar, Kategori, Satuan - Full CRUD for Superadmin & Apoteker + Route::middleware(['role:apoteker,superadmin'])->group(function () { + Route::resource('obat-masuk', ObatMasukController::class); + Route::resource('obat-keluar', ObatKeluarController::class); + Route::resource('kategori', KategoriController::class)->except(['show']); + Route::resource('satuan', SatuanController::class)->except(['show']); + }); + + // Laporan - Apoteker & Superadmin + Route::middleware(['role:apoteker,superadmin'])->group(function () { + Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan.index'); + Route::get('/laporan/export-pdf', [LaporanController::class, 'exportPdf'])->name('laporan.export-pdf'); + Route::get('/laporan/export-excel', [LaporanController::class, 'exportExcel'])->name('laporan.export-excel'); + }); + + // Superadmin only routes + Route::middleware(['role:superadmin'])->group(function () { + Route::resource('user-management', UserManagementController::class) + ->except(['show']) + ->parameters(['user-management' => 'user']); + }); + + // Profile (from Breeze) - All roles can access + Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); + Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); + Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); +}); + +require __DIR__.'/auth.php'; diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 0000000..fedb287 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 0000000..05c4471 --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 0000000..01e4a6c --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..c29eb1a --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,21 @@ +import defaultTheme from 'tailwindcss/defaultTheme'; +import forms from '@tailwindcss/forms'; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', + './storage/framework/views/*.php', + './resources/views/**/*.blade.php', + ], + + theme: { + extend: { + fontFamily: { + sans: ['Figtree', ...defaultTheme.fontFamily.sans], + }, + }, + }, + + plugins: [forms], +}; diff --git a/tests/DatabaseTestCase.php b/tests/DatabaseTestCase.php new file mode 100644 index 0000000..bf09a18 --- /dev/null +++ b/tests/DatabaseTestCase.php @@ -0,0 +1,31 @@ +artisan('migrate:fresh', [ + '--path' => 'database/migrations/safe', + '--realpath' => false, + ]); + } +} diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php new file mode 100644 index 0000000..13dcb7c --- /dev/null +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -0,0 +1,54 @@ +get('/login'); + + $response->assertStatus(200); + } + + public function test_users_can_authenticate_using_the_login_screen(): void + { + $user = User::factory()->create(); + + $response = $this->post('/login', [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(route('dashboard', absolute: false)); + } + + public function test_users_can_not_authenticate_with_invalid_password(): void + { + $user = User::factory()->create(); + + $this->post('/login', [ + 'email' => $user->email, + 'password' => 'wrong-password', + ]); + + $this->assertGuest(); + } + + public function test_users_can_logout(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/logout'); + + $this->assertGuest(); + $response->assertRedirect('/'); + } +} diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php new file mode 100644 index 0000000..705570b --- /dev/null +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -0,0 +1,58 @@ +unverified()->create(); + + $response = $this->actingAs($user)->get('/verify-email'); + + $response->assertStatus(200); + } + + public function test_email_can_be_verified(): void + { + $user = User::factory()->unverified()->create(); + + Event::fake(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + $this->assertTrue($user->fresh()->hasVerifiedEmail()); + $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); + } + + public function test_email_is_not_verified_with_invalid_hash(): void + { + $user = User::factory()->unverified()->create(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + $this->assertFalse($user->fresh()->hasVerifiedEmail()); + } +} diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php new file mode 100644 index 0000000..ff85721 --- /dev/null +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -0,0 +1,44 @@ +create(); + + $response = $this->actingAs($user)->get('/confirm-password'); + + $response->assertStatus(200); + } + + public function test_password_can_be_confirmed(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/confirm-password', [ + 'password' => 'password', + ]); + + $response->assertRedirect(); + $response->assertSessionHasNoErrors(); + } + + public function test_password_is_not_confirmed_with_invalid_password(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/confirm-password', [ + 'password' => 'wrong-password', + ]); + + $response->assertSessionHasErrors(); + } +} diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php new file mode 100644 index 0000000..aa50350 --- /dev/null +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -0,0 +1,73 @@ +get('/forgot-password'); + + $response->assertStatus(200); + } + + public function test_reset_password_link_can_be_requested(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + $this->post('/forgot-password', ['email' => $user->email]); + + Notification::assertSentTo($user, ResetPassword::class); + } + + public function test_reset_password_screen_can_be_rendered(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + $this->post('/forgot-password', ['email' => $user->email]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $response = $this->get('/reset-password/'.$notification->token); + + $response->assertStatus(200); + + return true; + }); + } + + public function test_password_can_be_reset_with_valid_token(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + $this->post('/forgot-password', ['email' => $user->email]); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $response = $this->post('/reset-password', [ + 'token' => $notification->token, + 'email' => $user->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('login')); + + return true; + }); + } +} diff --git a/tests/Feature/Auth/PasswordUpdateTest.php b/tests/Feature/Auth/PasswordUpdateTest.php new file mode 100644 index 0000000..ca28c6c --- /dev/null +++ b/tests/Feature/Auth/PasswordUpdateTest.php @@ -0,0 +1,51 @@ +create(); + + $response = $this + ->actingAs($user) + ->from('/profile') + ->put('/password', [ + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect('/profile'); + + $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); + } + + public function test_correct_password_must_be_provided_to_update_password(): void + { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->from('/profile') + ->put('/password', [ + 'current_password' => 'wrong-password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ]); + + $response + ->assertSessionHasErrorsIn('updatePassword', 'current_password') + ->assertRedirect('/profile'); + } +} diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php new file mode 100644 index 0000000..1489d0e --- /dev/null +++ b/tests/Feature/Auth/RegistrationTest.php @@ -0,0 +1,31 @@ +get('/register'); + + $response->assertStatus(200); + } + + public function test_new_users_can_register(): void + { + $response = $this->post('/register', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertRedirect(route('dashboard', absolute: false)); + } +} diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php new file mode 100644 index 0000000..118853b --- /dev/null +++ b/tests/Feature/DashboardTest.php @@ -0,0 +1,105 @@ +user = User::factory()->create(['role' => 'apoteker']); + $this->actingAs($this->user); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_dashboard_returns_200_for_authenticated_user(): void + { + $response = $this->get(route('dashboard')); + $response->assertStatus(200); + } + + public function test_dashboard_redirects_guest_to_login(): void + { + auth()->logout(); + $response = $this->get(route('dashboard')); + $response->assertRedirect(route('login')); + } + + public function test_dashboard_passes_required_variables_to_view(): void + { + $response = $this->get(route('dashboard')); + + $response->assertViewHas('totalJenisObat'); + $response->assertViewHas('obatMasukBulanIni'); + $response->assertViewHas('obatKeluarBulanIni'); + $response->assertViewHas('kadaluarsaDekat'); + $response->assertViewHas('labels'); + $response->assertViewHas('obatMasukData'); + $response->assertViewHas('obatKeluarData'); + $response->assertViewHas('monthOptions'); + $response->assertViewHas('filterMonth'); + $response->assertViewHas('filterYear'); + } + + public function test_dashboard_total_jenis_obat_counts_correctly(): void + { + $kategori = Kategori::create(['nama' => 'Antibiotik']); + $satuan = Satuan::create(['nama' => 'Tablet']); + + ObatMasuk::create([ + 'nama_obat' => 'Obat A', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'B1', + 'stok' => 10, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + ObatMasuk::create([ + 'nama_obat' => 'Obat B', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'B2', + 'stok' => 20, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + $response = $this->get(route('dashboard')); + $response->assertViewHas('totalJenisObat', 2); + } + + public function test_dashboard_month_options_has_12_entries(): void + { + $response = $this->get(route('dashboard')); + + $monthOptions = $response->viewData('monthOptions'); + $this->assertCount(12, $monthOptions); + } + + public function test_dashboard_filter_by_chart_month_and_year(): void + { + $response = $this->get(route('dashboard', [ + 'chart_month' => 1, + 'chart_year' => 2025, + ])); + + $response->assertStatus(200); + $response->assertViewHas('filterMonth', '1'); + $response->assertViewHas('filterYear', '2025'); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..3537631 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,20 @@ +get('/'); + + // Root URL redirects to login page + $response->assertRedirect(route('login')); + } +} diff --git a/tests/Feature/KadaluarsaTest.php b/tests/Feature/KadaluarsaTest.php new file mode 100644 index 0000000..f2af019 --- /dev/null +++ b/tests/Feature/KadaluarsaTest.php @@ -0,0 +1,150 @@ +batchSeq = 0; + $this->user = User::factory()->create(['role' => 'apoteker']); + $this->kategori = Kategori::create(['nama' => 'Antibiotik']); + $this->satuan = Satuan::create(['nama' => 'Tablet']); + $this->actingAs($this->user); + } + + private function createObat(string $namaObat, string $kadaluarsa, int $stok = 10): ObatMasuk + { + $this->batchSeq++; + return ObatMasuk::create([ + 'nama_obat' => $namaObat, + 'kategori_id' => $this->kategori->id, + 'satuan_id' => $this->satuan->id, + 'kode_batch' => 'BTH-' . str_pad($this->batchSeq, 3, '0', STR_PAD_LEFT), + 'stok' => $stok, + 'tanggal_penerimaan' => now()->subYear()->toDateString(), + 'tanggal_kadaluarsa' => $kadaluarsa, + 'user_id' => $this->user->id, + ]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200(): void + { + $response = $this->get(route('kadaluarsa.index')); + $response->assertStatus(200); + } + + public function test_index_redirects_guest(): void + { + auth()->logout(); + $response = $this->get(route('kadaluarsa.index')); + $response->assertRedirect(route('login')); + } + + public function test_index_shows_only_obat_expiring_within_120_days(): void + { + $this->createObat('ObatKritis', now()->addDays(15)->toDateString()); // โ‰ค 30 hari โœ“ + $this->createObat('ObatWaspada', now()->addDays(110)->toDateString()); // โ‰ค 120 hari โœ“ + $this->createObat('ObatAman', now()->addDays(130)->toDateString()); // > 120 hari โœ— + + $response = $this->get(route('kadaluarsa.index')); + $data = $response->viewData('obatKadaluarsa'); + + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatKritis', $names); + $this->assertContains('ObatWaspada', $names); + $this->assertNotContains('ObatAman', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Filter expired + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_filter_expired_shows_only_past_expiry(): void + { + $this->createObat('ObatExpired', now()->subDay()->toDateString()); + $this->createObat('ObatBelumExpired', now()->addDays(20)->toDateString()); + + $response = $this->get(route('kadaluarsa.index', ['filter' => 'expired'])); + $data = $response->viewData('obatKadaluarsa'); + + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatExpired', $names); + $this->assertNotContains('ObatBelumExpired', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Filter 30 + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_filter_30_shows_obat_expiring_within_30_days(): void + { + $this->createObat('ObatKritis', now()->addDays(20)->toDateString()); // โ‰ค 30 hari โœ“ + $this->createObat('ObatWaspada', now()->addDays(50)->toDateString()); // > 30 hari โœ— + + $response = $this->get(route('kadaluarsa.index', ['filter' => '30'])); + $data = $response->viewData('obatKadaluarsa'); + + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatKritis', $names); + $this->assertNotContains('ObatWaspada', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Filter 120 + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_filter_120_shows_obat_expiring_within_120_days(): void + { + $this->createObat('ObatDalamRange', now()->addDays(110)->toDateString()); // โ‰ค 120 hari โœ“ + $this->createObat('ObatAman', now()->addDays(130)->toDateString()); // > 120 hari โœ— + + $response = $this->get(route('kadaluarsa.index', ['filter' => '120'])); + $data = $response->viewData('obatKadaluarsa'); + + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatDalamRange', $names); + $this->assertNotContains('ObatAman', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Filter all (default) + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_filter_all_shows_all_obat_within_120_days(): void + { + $this->createObat('ObatExpired', now()->subDays(5)->toDateString()); // expired โœ“ + $this->createObat('ObatKritis', now()->addDays(25)->toDateString()); // โ‰ค 30 hari โœ“ + $this->createObat('ObatWaspada', now()->addDays(110)->toDateString()); // โ‰ค 120 hari โœ“ + + $response = $this->get(route('kadaluarsa.index', ['filter' => 'all'])); + $data = $response->viewData('obatKadaluarsa'); + + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatExpired', $names); + $this->assertContains('ObatKritis', $names); + $this->assertContains('ObatWaspada', $names); + } + + public function test_passes_filter_variable_to_view(): void + { + $response = $this->get(route('kadaluarsa.index', ['filter' => 'expired'])); + $response->assertViewHas('filter', 'expired'); + } +} diff --git a/tests/Feature/KategoriTest.php b/tests/Feature/KategoriTest.php new file mode 100644 index 0000000..09e7180 --- /dev/null +++ b/tests/Feature/KategoriTest.php @@ -0,0 +1,164 @@ +create(['role' => 'apoteker']); + $this->actingAs($user); + return $user; + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200_for_authenticated_user(): void + { + $this->actingAsApoteker(); + $response = $this->get(route('kategori.index')); + $response->assertStatus(200); + } + + public function test_index_redirects_guest_to_login(): void + { + $response = $this->get(route('kategori.index')); + $response->assertRedirect(route('login')); + } + + public function test_index_lists_all_kategoris(): void + { + $this->actingAsApoteker(); + Kategori::create(['nama' => 'Antibiotik']); + Kategori::create(['nama' => 'Vitamin']); + + $response = $this->get(route('kategori.index')); + $response->assertSee('Antibiotik'); + $response->assertSee('Vitamin'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_creates_kategori_with_valid_data(): void + { + $this->actingAsApoteker(); + + $response = $this->post(route('kategori.store'), [ + 'nama' => 'Antibiotik', + 'keterangan' => 'Obat antibiotik', + ]); + + $response->assertRedirect(route('kategori.index')); + $response->assertSessionHas('success'); + $this->assertDatabaseHas('kategoris', ['nama' => 'Antibiotik']); + } + + public function test_store_fails_without_nama(): void + { + $this->actingAsApoteker(); + + $response = $this->post(route('kategori.store'), [ + 'nama' => '', + ]); + + $response->assertSessionHasErrors('nama'); + } + + public function test_store_fails_with_duplicate_nama(): void + { + $this->actingAsApoteker(); + Kategori::create(['nama' => 'Antibiotik']); + + $response = $this->post(route('kategori.store'), [ + 'nama' => 'Antibiotik', + ]); + + $response->assertSessionHasErrors('nama'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_modifies_kategori(): void + { + $this->actingAsApoteker(); + $kategori = Kategori::create(['nama' => 'Antibiotik']); + + $response = $this->put(route('kategori.update', $kategori), [ + 'nama' => 'Anti Inflamasi', + 'keterangan' => 'Updated', + ]); + + $response->assertRedirect(route('kategori.index')); + $this->assertDatabaseHas('kategoris', ['nama' => 'Anti Inflamasi']); + $this->assertDatabaseMissing('kategoris', ['nama' => 'Antibiotik']); + } + + public function test_update_allows_same_nama_for_same_kategori(): void + { + $this->actingAsApoteker(); + $kategori = Kategori::create(['nama' => 'Antibiotik']); + + $response = $this->put(route('kategori.update', $kategori), [ + 'nama' => 'Antibiotik', + 'keterangan' => 'Edited keterangan', + ]); + + $response->assertRedirect(route('kategori.index')); + $this->assertDatabaseHas('kategoris', [ + 'id' => $kategori->id, + 'nama' => 'Antibiotik', + 'keterangan' => 'Edited keterangan', + ]); + } + + public function test_update_fails_with_duplicate_nama_of_other_kategori(): void + { + $this->actingAsApoteker(); + Kategori::create(['nama' => 'Vitamin']); + $kategori = Kategori::create(['nama' => 'Antibiotik']); + + $response = $this->put(route('kategori.update', $kategori), [ + 'nama' => 'Vitamin', + ]); + + $response->assertSessionHasErrors('nama'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_kategori(): void + { + $this->actingAsApoteker(); + $kategori = Kategori::create(['nama' => 'Antibiotik']); + + $response = $this->delete(route('kategori.destroy', $kategori)); + + $response->assertRedirect(route('kategori.index')); + $this->assertDatabaseMissing('kategoris', ['id' => $kategori->id]); + } + + public function test_superadmin_can_crud_kategori(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $this->get(route('kategori.create'))->assertStatus(200); + $this->post(route('kategori.store'), ['nama' => 'Herbal'])->assertRedirect(route('kategori.index')); + $kategori = Kategori::where('nama', 'Herbal')->firstOrFail(); + $this->get(route('kategori.edit', $kategori))->assertStatus(200); + $this->put(route('kategori.update', $kategori), ['nama' => 'Herbal Update'])->assertRedirect(route('kategori.index')); + $this->delete(route('kategori.destroy', $kategori))->assertRedirect(route('kategori.index')); + } +} diff --git a/tests/Feature/LaporanTest.php b/tests/Feature/LaporanTest.php new file mode 100644 index 0000000..f2a559a --- /dev/null +++ b/tests/Feature/LaporanTest.php @@ -0,0 +1,154 @@ +user = User::factory()->create(['role' => 'apoteker']); + $this->aktegori = Kategori::create(['nama' => 'Antibiotik']); + $this->satuan = Satuan::create(['nama' => 'Tablet']); + $this->kategori = $this->aktegori ?? Kategori::first(); + $this->actingAs($this->user); + } + + private function createObatMasuk(string $tanggal): ObatMasuk + { + static $counter = 0; + $counter++; + + return ObatMasuk::create([ + 'nama_obat' => 'Obat-' . $counter, + 'kategori_id' => $this->kategori->id, + 'satuan_id' => $this->satuan->id, + 'kode_batch' => 'BTH-' . $counter, + 'stok' => 50, + 'tanggal_penerimaan' => $tanggal, + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + } + + private function createObatKeluar(string $tanggal, int $jumlah = 5): ObatKeluar + { + static $counter = 0; + $counter++; + $obatMasuk = $this->createObatMasuk(now()->subMonth()->toDateString()); + + return ObatKeluar::create([ + 'obat_masuk_id' => $obatMasuk->id, + 'nama_obat' => 'ObatKeluar-' . $counter, + 'kode_batch' => 'BTH-KEL-' . $counter, + 'jumlah' => $jumlah, + 'tujuan_pemakaian' => 'Test', + 'tanggal_pengeluaran' => $tanggal, + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'nama_petugas' => 'Petugas', + 'nama_penerima' => 'Penerima', + 'status' => 'proses', + 'user_id' => $this->user->id, + ]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_laporan_index_returns_200(): void + { + $response = $this->get(route('laporan.index')); + $response->assertStatus(200); + } + + public function test_laporan_redirects_guest_to_login(): void + { + auth()->logout(); + $response = $this->get(route('laporan.index')); + $response->assertRedirect(route('login')); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Jenis masuk + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_laporan_jenis_masuk_filters_by_date_range(): void + { + $tanggalDalam = '2025-03-05'; + $tanggalLuar = '2025-01-10'; + + $this->createObatMasuk($tanggalDalam); + $this->createObatMasuk($tanggalLuar); + + $response = $this->get(route('laporan.index', [ + 'jenis' => 'masuk', + 'tanggal_mulai' => '2025-03-01', + 'tanggal_akhir' => '2025-03-31', + ])); + + $response->assertStatus(200); + $response->assertViewHas('jenis', 'masuk'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Jenis keluar + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_laporan_jenis_keluar_filters_by_date_range(): void + { + $tanggal = now()->toDateString(); + $this->createObatKeluar($tanggal, 5); + + $response = $this->get(route('laporan.index', [ + 'jenis' => 'keluar', + 'tanggal_mulai' => now()->startOfMonth()->toDateString(), + 'tanggal_akhir' => now()->endOfMonth()->toDateString(), + ])); + + $response->assertStatus(200); + $response->assertViewHas('jenis', 'keluar'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // View variables + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_laporan_passes_required_view_variables(): void + { + $response = $this->get(route('laporan.index')); + + $response->assertViewHas('data'); + $response->assertViewHas('jenis'); + $response->assertViewHas('tanggalMulai'); + $response->assertViewHas('tanggalAkhir'); + } + + public function test_laporan_defaults_to_jenis_masuk(): void + { + $response = $this->get(route('laporan.index')); + $response->assertViewHas('jenis', 'masuk'); + } + + public function test_laporan_defaults_tanggal_to_current_month(): void + { + $response = $this->get(route('laporan.index')); + + $tanggalMulai = $response->viewData('tanggalMulai'); + $tanggalAkhir = $response->viewData('tanggalAkhir'); + + $this->assertEquals(now()->startOfMonth()->format('Y-m-d'), $tanggalMulai); + $this->assertEquals(now()->endOfMonth()->format('Y-m-d'), $tanggalAkhir); + } +} diff --git a/tests/Feature/ObatKeluarTest.php b/tests/Feature/ObatKeluarTest.php new file mode 100644 index 0000000..508e38b --- /dev/null +++ b/tests/Feature/ObatKeluarTest.php @@ -0,0 +1,363 @@ +user = User::factory()->create(['role' => 'apoteker']); + $this->actingAs($this->user); + + $kategori = Kategori::create(['nama' => 'Antibiotik']); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $this->obatMasuk = ObatMasuk::create([ + 'nama_obat' => 'Amoxicillin', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'BTH-001', + 'stok' => 100, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + } + + private function validData(array $overrides = []): array + { + return array_merge([ + 'obat_masuk_id' => $this->obatMasuk->id, + 'kode_batch' => 'BTH-001', + 'jumlah' => 10, + 'tujuan_pemakaian' => 'Penggunaan internal', + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'tanggal_pengeluaran' => now()->toDateString(), + 'nama_petugas' => 'Petugas A', + 'nama_penerima' => 'Penerima B', + ], $overrides); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200(): void + { + $response = $this->get(route('obat-keluar.index')); + $response->assertStatus(200); + } + + public function test_index_guest_redirects_to_login(): void + { + auth()->logout(); + $response = $this->get(route('obat-keluar.index')); + $response->assertRedirect(route('login')); + } + + public function test_index_filter_by_status(): void + { + ObatKeluar::create(array_merge($this->validData(['jumlah' => 5]), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + + $response = $this->get(route('obat-keluar.index', ['status' => 'proses'])); + $response->assertStatus(200); + } + + public function test_index_search(): void + { + ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Amoxicillin', + 'kode_batch' => 'BTH-001', + 'user_id' => $this->user->id, + ])); + + ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Paracetamol', + 'kode_batch' => 'BTH-002', + 'user_id' => $this->user->id, + ])); + + $response = $this->get(route('obat-keluar.index', ['search' => 'Paracetamol'])); + $data = $response->viewData('obatKeluars'); + $names = $data->pluck('nama_obat')->toArray(); + + $this->assertContains('Paracetamol', $names); + $this->assertNotContains('Amoxicillin', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // create, show, edit + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_create_returns_200(): void + { + $response = $this->get(route('obat-keluar.create')); + $response->assertStatus(200); + } + + public function test_show_returns_200(): void + { + $keluar = ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $response = $this->get(route('obat-keluar.show', $keluar)); + $response->assertStatus(200); + } + + public function test_edit_returns_200(): void + { + $keluar = ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $response = $this->get(route('obat-keluar.edit', $keluar)); + $response->assertStatus(200); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store โ€” berhasil + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_creates_obat_keluar_and_decrements_stok(): void + { + $initialStok = $this->obatMasuk->stok; // 100 + + $response = $this->post(route('obat-keluar.store'), $this->validData(['jumlah' => 10])); + + $response->assertRedirect(route('obat-keluar.index')); + $response->assertSessionHas('success'); + + $this->assertDatabaseHas('obat_keluars', [ + 'obat_masuk_id' => $this->obatMasuk->id, + 'jumlah' => 10, + 'nama_obat' => 'Amoxicillin', + ]); + + // Stok berkurang dari 100 menjadi 90 + $this->assertEquals($initialStok - 10, $this->obatMasuk->fresh()->stok); + } + + public function test_store_sets_user_id_and_nama_obat_automatically(): void + { + $this->post(route('obat-keluar.store'), $this->validData(['jumlah' => 5])); + + $this->assertDatabaseHas('obat_keluars', [ + 'user_id' => $this->user->id, + 'nama_obat' => 'Amoxicillin', + ]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store โ€” gagal karena stok + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_fails_when_jumlah_exceeds_stok(): void + { + $response = $this->post(route('obat-keluar.store'), $this->validData(['jumlah' => 200])); // stok cuma 100 + + // Harus kembali ke form dengan error jumlah + $response->assertSessionHasErrors('jumlah'); + } + + public function test_store_stok_unchanged_when_validation_fails(): void + { + $this->post(route('obat-keluar.store'), $this->validData(['jumlah' => 200])); + + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store โ€” validasi field wajib + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_fails_without_required_fields(): void + { + $response = $this->post(route('obat-keluar.store'), []); + $response->assertSessionHasErrors([ + 'obat_masuk_id', + 'kode_batch', + 'jumlah', + 'tujuan_pemakaian', + 'tanggal_kadaluarsa', + 'tanggal_pengeluaran', + 'nama_petugas', + 'nama_penerima', + ]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update โ€” perubahan jumlah + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_adjusts_stok_when_jumlah_increases(): void + { + // Buat obat keluar awal dengan jumlah 10 โ†’ stok jadi 90 + $keluar = ObatKeluar::create(array_merge($this->validData(['jumlah' => 10]), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $this->obatMasuk->decrement('stok', 10); // stok = 90 + + // Update jumlah menjadi 30 (selisih +20, stok dari 90 โ†’ 70) + $response = $this->put( + route('obat-keluar.update', $keluar), + $this->validData(['jumlah' => 30]) + ); + + $response->assertRedirect(route('obat-keluar.index')); + $this->assertEquals(70, $this->obatMasuk->fresh()->stok); + } + + public function test_update_adjusts_stok_when_jumlah_decreases(): void + { + // Buat obat keluar awal dengan jumlah 20 โ†’ stok jadi 80 + $keluar = ObatKeluar::create(array_merge($this->validData(['jumlah' => 20]), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $this->obatMasuk->decrement('stok', 20); // stok = 80 + + // Update jumlah menjadi 10 (selisih โˆ’10, stok dari 80 โ†’ 90) + $response = $this->put( + route('obat-keluar.update', $keluar), + $this->validData(['jumlah' => 10]) + ); + + $response->assertRedirect(route('obat-keluar.index')); + $this->assertEquals(90, $this->obatMasuk->fresh()->stok); + } + + public function test_update_fails_when_increased_jumlah_exceeds_available_stok(): void + { + // Stok awal 100, keluar 10 โ†’ stok = 90 + $keluar = ObatKeluar::create(array_merge($this->validData(['jumlah' => 10]), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $this->obatMasuk->decrement('stok', 10); + + // Coba update ke 200 (selisih = +190, stok hanya 90) + $response = $this->put( + route('obat-keluar.update', $keluar), + $this->validData(['jumlah' => 200]) + ); + + $response->assertSessionHasErrors('jumlah'); + } + + public function test_update_fails_without_required_fields(): void + { + $keluar = ObatKeluar::create(array_merge($this->validData(['jumlah' => 10]), [ + 'nama_obat' => 'Amoxicillin', + 'user_id' => $this->user->id, + ])); + + $response = $this->put(route('obat-keluar.update', $keluar), []); + + $response->assertSessionHasErrors([ + 'obat_masuk_id', + 'kode_batch', + 'jumlah', + 'tujuan_pemakaian', + 'tanggal_kadaluarsa', + 'tanggal_pengeluaran', + 'nama_petugas', + 'nama_penerima', + ]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy โ€” stok dikembalikan + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_obat_keluar_and_restores_stok(): void + { + // Buat keluar jumlah 15 โ†’ stok jadi 85 + $keluar = ObatKeluar::create(array_merge($this->validData(['jumlah' => 15]), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $this->obatMasuk->decrement('stok', 15); + + $response = $this->delete(route('obat-keluar.destroy', $keluar)); + + $response->assertRedirect(route('obat-keluar.index')); + $response->assertSessionHas('success'); + + // Stok dikembalikan โ†’ 100 + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + $this->assertDatabaseMissing('obat_keluars', ['id' => $keluar->id]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // superadmin access + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_superadmin_can_access_index(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $response = $this->get(route('obat-keluar.index')); + $response->assertStatus(200); + } + + public function test_superadmin_can_access_show(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $keluar = ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + $response = $this->get(route('obat-keluar.show', $keluar)); + $response->assertStatus(200); + } + + public function test_superadmin_can_access_create_store_edit_update_destroy(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $keluar = ObatKeluar::create(array_merge($this->validData(), [ + 'nama_obat' => 'Amoxicillin', + 'status' => 'proses', + 'user_id' => $this->user->id, + ])); + + $this->get(route('obat-keluar.create'))->assertStatus(200); + $this->post(route('obat-keluar.store'), $this->validData(['jumlah' => 10])) + ->assertRedirect(route('obat-keluar.index')); + $this->assertDatabaseHas('obat_keluars', ['user_id' => $superadmin->id, 'jumlah' => 10]); + $this->get(route('obat-keluar.edit', $keluar))->assertStatus(200); + $this->put(route('obat-keluar.update', $keluar), $this->validData(['jumlah' => 8])) + ->assertRedirect(route('obat-keluar.index')); + $this->assertDatabaseHas('obat_keluars', ['id' => $keluar->id, 'jumlah' => 8]); + $this->delete(route('obat-keluar.destroy', $keluar))->assertRedirect(route('obat-keluar.index')); + $this->assertDatabaseMissing('obat_keluars', ['id' => $keluar->id]); + } +} diff --git a/tests/Feature/ObatMasukTest.php b/tests/Feature/ObatMasukTest.php new file mode 100644 index 0000000..0dea624 --- /dev/null +++ b/tests/Feature/ObatMasukTest.php @@ -0,0 +1,290 @@ +user = User::factory()->create(['role' => 'apoteker']); + $this->actingAs($this->user); + } + + private function createKategori(): Kategori + { + return Kategori::create(['nama' => 'Antibiotik']); + } + + private function createSatuan(): Satuan + { + return Satuan::create(['nama' => 'Tablet']); + } + + private function validData(array $overrides = []): array + { + $kategori = Kategori::firstOrCreate(['nama' => 'Antibiotik']); + $satuan = Satuan::firstOrCreate(['nama' => 'Tablet']); + + return array_merge([ + 'nama_obat' => 'Amoxicillin', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'BTH-001', + 'stok' => 100, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + ], $overrides); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200(): void + { + $response = $this->get(route('obat-masuk.index')); + $response->assertStatus(200); + } + + public function test_index_guest_redirects_to_login(): void + { + auth()->logout(); + $response = $this->get(route('obat-masuk.index')); + $response->assertRedirect(route('login')); + } + + public function test_index_search_by_nama_obat(): void + { + $kategori = $this->createKategori(); + $satuan = $this->createSatuan(); + + ObatMasuk::create([ + 'nama_obat' => 'Amoxicillin', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'BTH-001', + 'stok' => 100, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + ObatMasuk::create([ + 'nama_obat' => 'Paracetamol', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'BTH-002', + 'stok' => 50, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + $response = $this->get(route('obat-masuk.index', ['search' => 'Amoxicillin'])); + $data = $response->viewData('obatMasuks'); + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('Amoxicillin', $names); + $this->assertNotContains('Paracetamol', $names); + } + + public function test_index_filter_by_status_tersedia(): void + { + $kategori = $this->createKategori(); + $satuan = $this->createSatuan(); + + ObatMasuk::create([ + 'nama_obat' => 'ObatA', 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, 'kode_batch' => 'B1', 'stok' => 10, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + ObatMasuk::create([ + 'nama_obat' => 'ObatB', 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, 'kode_batch' => 'B2', 'stok' => 0, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + $response = $this->get(route('obat-masuk.index', ['status' => 'tersedia'])); + $data = $response->viewData('obatMasuks'); + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatA', $names); + $this->assertNotContains('ObatB', $names); + } + + public function test_index_filter_by_status_habis(): void + { + $kategori = $this->createKategori(); + $satuan = $this->createSatuan(); + + ObatMasuk::create([ + 'nama_obat' => 'ObatA', 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, 'kode_batch' => 'B1', 'stok' => 10, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + ObatMasuk::create([ + 'nama_obat' => 'ObatHabis', 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, 'kode_batch' => 'B2', 'stok' => 0, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->user->id, + ]); + + $response = $this->get(route('obat-masuk.index', ['status' => 'habis'])); + $data = $response->viewData('obatMasuks'); + $names = $data->pluck('nama_obat')->toArray(); + $this->assertContains('ObatHabis', $names); + $this->assertNotContains('ObatA', $names); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_creates_obat_masuk_with_valid_data(): void + { + $response = $this->post(route('obat-masuk.store'), $this->validData()); + + $response->assertRedirect(route('obat-masuk.index')); + $response->assertSessionHas('success'); + $this->assertDatabaseHas('obat_masuks', ['nama_obat' => 'Amoxicillin', 'stok' => 100]); + } + + public function test_store_fails_without_required_fields(): void + { + $response = $this->post(route('obat-masuk.store'), []); + $response->assertSessionHasErrors(['nama_obat', 'kode_batch', 'stok', 'tanggal_penerimaan', 'tanggal_kadaluarsa']); + } + + public function test_store_fails_when_kadaluarsa_before_penerimaan(): void + { + $response = $this->post(route('obat-masuk.store'), $this->validData([ + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->subDay()->toDateString(), // sebelum penerimaan + ])); + + $response->assertSessionHasErrors('tanggal_kadaluarsa'); + } + + public function test_store_fails_with_zero_stok(): void + { + $response = $this->post(route('obat-masuk.store'), $this->validData(['stok' => 0])); + $response->assertSessionHasErrors('stok'); + } + + public function test_store_sets_user_id_from_authenticated_user(): void + { + $this->post(route('obat-masuk.store'), $this->validData()); + $this->assertDatabaseHas('obat_masuks', ['user_id' => $this->user->id]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // show + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_show_returns_200(): void + { + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + $response = $this->get(route('obat-masuk.show', $obat)); + $response->assertStatus(200); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_modifies_obat_masuk(): void + { + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + $response = $this->put(route('obat-masuk.update', $obat), $this->validData(['nama_obat' => 'Paracetamol', 'stok' => 50])); + + $response->assertRedirect(route('obat-masuk.index')); + $this->assertDatabaseHas('obat_masuks', ['id' => $obat->id, 'nama_obat' => 'Paracetamol', 'stok' => 50]); + } + + public function test_update_fails_without_required_fields(): void + { + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + $response = $this->put(route('obat-masuk.update', $obat), []); + $response->assertSessionHasErrors(['nama_obat', 'kode_batch']); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_obat_masuk(): void + { + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + $response = $this->delete(route('obat-masuk.destroy', $obat)); + + $response->assertRedirect(route('obat-masuk.index')); + $this->assertDatabaseMissing('obat_masuks', ['id' => $obat->id]); + } + + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // superadmin access + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_superadmin_can_access_index(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $response = $this->get(route('obat-masuk.index')); + $response->assertStatus(200); + } + + public function test_superadmin_can_access_show(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + $response = $this->get(route('obat-masuk.show', $obat)); + $response->assertStatus(200); + } + + public function test_superadmin_can_access_create_store_edit_update_destroy(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $obat = ObatMasuk::create(array_merge($this->validData(), ['user_id' => $this->user->id])); + + $this->get(route('obat-masuk.create'))->assertStatus(200); + + $storePayload = $this->validData([ + 'nama_obat' => 'Cefixime', + 'kode_batch' => 'BTH-003', + ]); + $this->post(route('obat-masuk.store'), $storePayload)->assertRedirect(route('obat-masuk.index')); + $this->assertDatabaseHas('obat_masuks', ['nama_obat' => 'Cefixime']); + + $this->get(route('obat-masuk.edit', $obat))->assertStatus(200); + + $updatePayload = $this->validData(['nama_obat' => 'Paracetamol Superadmin']); + $this->put(route('obat-masuk.update', $obat), $updatePayload)->assertRedirect(route('obat-masuk.index')); + $this->assertDatabaseHas('obat_masuks', ['id' => $obat->id, 'nama_obat' => 'Paracetamol Superadmin']); + + $this->delete(route('obat-masuk.destroy', $obat))->assertRedirect(route('obat-masuk.index')); + $this->assertDatabaseMissing('obat_masuks', ['id' => $obat->id]); + } +} diff --git a/tests/Feature/ProfileTest.php b/tests/Feature/ProfileTest.php new file mode 100644 index 0000000..3394b37 --- /dev/null +++ b/tests/Feature/ProfileTest.php @@ -0,0 +1,101 @@ +create(); + + $response = $this + ->actingAs($user) + ->get('/profile'); + + $response->assertOk(); + } + + public function test_profile_information_can_be_updated(): void + { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->patch('/profile', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'gender' => 'laki-laki', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('profile.edit')); + + $user->refresh(); + + $this->assertSame('Test User', $user->name); + $this->assertSame('test@example.com', $user->email); + $this->assertNull($user->email_verified_at); + } + + public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void + { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->patch('/profile', [ + 'name' => 'Test User', + 'email' => $user->email, + 'gender' => 'perempuan', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('profile.edit')); + + $this->assertNotNull($user->refresh()->email_verified_at); + } + + public function test_user_can_delete_their_account(): void + { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->delete('/profile', [ + 'password' => 'password', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect('/'); + + $this->assertGuest(); + $this->assertNull($user->fresh()); + } + + public function test_correct_password_must_be_provided_to_delete_account(): void + { + $user = User::factory()->create(); + + $response = $this + ->actingAs($user) + ->from('/profile') + ->delete('/profile', [ + 'password' => 'wrong-password', + ]); + + $response + ->assertSessionHasErrorsIn('userDeletion', 'password') + ->assertRedirect('/profile'); + + $this->assertNotNull($user->fresh()); + } +} diff --git a/tests/Feature/ResepTest.php b/tests/Feature/ResepTest.php new file mode 100644 index 0000000..038719c --- /dev/null +++ b/tests/Feature/ResepTest.php @@ -0,0 +1,495 @@ +dokter = User::factory()->create(['role' => 'dokter']); + $this->apoteker = User::factory()->create(['role' => 'apoteker']); + + $kategori = Kategori::create(['nama' => 'Antibiotik']); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $this->obatMasuk = ObatMasuk::create([ + 'nama_obat' => 'Amoxicillin', + 'kategori_id' => $kategori->id, + 'satuan_id' => $satuan->id, + 'kode_batch' => 'BTH-001', + 'stok' => 100, + 'tanggal_penerimaan' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'user_id' => $this->dokter->id, + ]); + } + + private function resepData(array $overrides = []): array + { + return array_merge([ + 'nama_dokter' => 'Dr. Siti', + 'tanggal_resep' => now()->toDateString(), + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'nama_pasien' => 'Budi Santoso', + 'jenis_kelamin' => 'L', + 'umur_pasien' => 30, + 'berat_badan' => 65.5, + 'diagnosa' => 'ISPA', + 'items' => [ + [ + 'obat_masuk_id' => $this->obatMasuk->id, + 'jumlah' => 10, + 'aturan_pakai' => '3x sehari', + ], + ], + ], $overrides); + } + + private function createResep(array $attrs = []): Resep + { + return Resep::create(array_merge([ + 'no_resep' => 'RSP-TEST-' . rand(1000, 9999), + 'user_id' => $this->dokter->id, + 'nama_dokter' => 'Dr. Siti', + 'nama_pasien' => 'Budi Santoso', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Flu', + 'status' => 'proses', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'L', + 'umur_pasien' => 30, + 'berat_badan' => 60, + 'is_read' => false, + ], $attrs)); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200_for_authenticated_user(): void + { + $this->actingAs($this->dokter); + $response = $this->get(route('resep.index')); + $response->assertStatus(200); + } + + public function test_index_redirects_guest(): void + { + $response = $this->get(route('resep.index')); + $response->assertRedirect(route('login')); + } + + public function test_index_search_by_nama_pasien(): void + { + $this->actingAs($this->dokter); + $this->createResep(['nama_pasien' => 'Budi Santoso', 'no_resep' => 'RSP-001']); + $this->createResep(['nama_pasien' => 'Siti Rahayu', 'no_resep' => 'RSP-002']); + + $response = $this->get(route('resep.index', ['search' => 'Budi'])); + $response->assertSee('Budi Santoso'); + $response->assertDontSee('Siti Rahayu'); + } + + public function test_index_filter_by_status(): void + { + $this->actingAs($this->dokter); + $this->createResep(['no_resep' => 'RSP-001', 'status' => 'proses']); + $this->createResep(['no_resep' => 'RSP-002', 'status' => 'selesai', 'nama_pasien' => 'Pasien Selesai']); + + $response = $this->get(route('resep.index', ['status' => 'proses'])); + $response->assertSee('Budi Santoso'); + $response->assertDontSee('Pasien Selesai'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store โ€” berhasil + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_creates_resep_with_items_and_obat_keluar(): void + { + $this->actingAs($this->dokter); + + $response = $this->post(route('resep.store'), $this->resepData()); + + $response->assertRedirect(route('resep.index')); + $response->assertSessionHas('success'); + + // Resep terbuat + $this->assertDatabaseHas('reseps', ['nama_pasien' => 'Budi Santoso', 'status' => 'proses']); + + // ResepItem terbuat + $this->assertDatabaseHas('resep_items', [ + 'obat_masuk_id' => $this->obatMasuk->id, + 'jumlah' => 10, + ]); + + // ObatKeluar terbuat + $this->assertDatabaseHas('obat_keluars', [ + 'obat_masuk_id' => $this->obatMasuk->id, + 'jumlah' => 10, + 'status' => 'proses', + ]); + } + + public function test_store_does_not_decrement_stok_when_status_is_proses(): void + { + $this->actingAs($this->dokter); + + $this->post(route('resep.store'), $this->resepData([ + 'items' => [ + ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 15, 'aturan_pakai' => '3x sehari'], + ], + ])); + + // Stok dari 100 tetap 100 karena statusnya 'proses' + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + } + + public function test_store_generates_unique_no_resep(): void + { + $this->actingAs($this->dokter); + + $this->post(route('resep.store'), $this->resepData()); + + $resep = Resep::first(); + $this->assertStringStartsWith('RSP-', $resep->no_resep); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store โ€” gagal karena stok + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_fails_when_item_stok_insufficient(): void + { + $this->actingAs($this->dokter); + + $response = $this->post(route('resep.store'), $this->resepData([ + 'items' => [ + ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 200, 'aturan_pakai' => '1x sehari'], + ], + ])); + + $response->assertSessionHasErrors('items'); + $this->assertDatabaseCount('reseps', 0); + } + + public function test_store_fails_without_required_fields(): void + { + $this->actingAs($this->dokter); + + $response = $this->post(route('resep.store'), []); + $response->assertSessionHasErrors(['nama_dokter', 'tanggal_resep', 'nama_pasien', 'diagnosa', 'items']); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // show + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_show_returns_200(): void + { + $this->actingAs($this->apoteker); + $resep = $this->createResep(); + + $response = $this->get(route('resep.show', $resep)); + $response->assertStatus(200); + } + + public function test_show_marks_as_read_for_apoteker(): void + { + $this->actingAs($this->apoteker); + $resep = $this->createResep(['is_read' => false]); + + $this->get(route('resep.show', $resep)); + + $this->assertTrue($resep->fresh()->is_read); + } + + public function test_show_does_not_mark_as_read_for_dokter(): void + { + $this->actingAs($this->dokter); + $resep = $this->createResep(['is_read' => false]); + + $this->get(route('resep.show', $resep)); + + $this->assertFalse($resep->fresh()->is_read); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // edit โ€” restrict selesai + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_edit_redirects_to_show_when_status_is_selesai(): void + { + $this->actingAs($this->dokter); + $resep = $this->createResep(['status' => 'selesai']); + + $response = $this->get(route('resep.edit', $resep)); + $response->assertRedirect(route('resep.show', $resep)); + $response->assertSessionHas('error'); + } + + public function test_edit_returns_200_when_status_is_proses(): void + { + $this->actingAs($this->dokter); + $resep = $this->createResep(['status' => 'proses']); + + $response = $this->get(route('resep.edit', $resep)); + $response->assertStatus(200); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update โ€” restrict selesai + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_redirects_to_show_when_resep_is_selesai(): void + { + $this->actingAs($this->dokter); + $resep = $this->createResep(['status' => 'selesai']); + + $response = $this->put(route('resep.update', $resep), $this->resepData(['status' => 'proses'])); + $response->assertRedirect(route('resep.show', $resep)); + $response->assertSessionHas('error'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update โ€” berhasil + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_does_not_deduct_stok_when_status_is_proses(): void + { + $this->actingAs($this->dokter); + + // Buat resep awal (status proses, stok utuh 100) + $resep = $this->createResep(); + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'jumlah' => 10, + ]); + + // Update dengan item baru jumlah 20, status tetap proses + $response = $this->put(route('resep.update', $resep), $this->resepData([ + 'status' => 'proses', + 'items' => [ + ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 20, 'aturan_pakai' => '1x sehari'], + ], + ])); + + $response->assertRedirect(route('resep.index')); + // Stok tetap 100 karena masih proses + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + } + + public function test_update_deducts_stok_when_status_changed_to_selesai(): void + { + $this->actingAs($this->dokter); + + // Buat resep awal (status proses, stok utuh 100) + $resep = $this->createResep(); + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'jumlah' => 10, + ]); + + // Update status menjadi selesai dan jumlah menjadi 20 + $response = $this->put(route('resep.update', $resep), $this->resepData([ + 'status' => 'selesai', + 'items' => [ + ['obat_masuk_id' => $this->obatMasuk->id, 'jumlah' => 20, 'aturan_pakai' => '1x sehari'], + ], + ])); + + $response->assertRedirect(route('resep.index')); + // Stok: berkurang 20 โ†’ 80 + $this->assertEquals(80, $this->obatMasuk->fresh()->stok); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // updateStatus + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_status_changes_resep_and_obat_keluar_status(): void + { + $this->actingAs($this->apoteker); + $resep = $this->createResep(['status' => 'proses']); + + // Buat item resep + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'jumlah' => 10, + ]); + + // Buat obat keluar terkait dengan no_pengeluaran = no_resep + ObatKeluar::create([ + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'no_pengeluaran' => $resep->no_resep, + 'jumlah' => 10, + 'tujuan_pemakaian' => 'Resep', + 'tanggal_pengeluaran' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'nama_petugas' => 'Petugas', + 'nama_penerima' => 'Pasien', + 'kode_batch' => 'BTH-001', + 'status' => 'proses', + 'user_id' => $this->apoteker->id, + ]); + + $response = $this->patch(route('resep.update-status', $resep), [ + 'status' => 'selesai', + ]); + + $response->assertRedirect(route('resep.index')); + $this->assertEquals('selesai', $resep->fresh()->status); + + // ObatKeluar terkait juga diupdate + $this->assertDatabaseHas('obat_keluars', [ + 'no_pengeluaran' => $resep->no_resep, + 'status' => 'selesai', + ]); + + // Stok harus berkurang karena status berubah menjadi selesai + $this->assertEquals(90, $this->obatMasuk->fresh()->stok); + } + + public function test_update_status_fails_when_resep_already_selesai(): void + { + $this->actingAs($this->apoteker); + $resep = $this->createResep(['status' => 'selesai']); + + $response = $this->patch(route('resep.update-status', $resep), [ + 'status' => 'dibatalkan', + ]); + + $response->assertRedirect(route('resep.index')); + $response->assertSessionHas('error'); + + // Status tidak berubah + $this->assertEquals('selesai', $resep->fresh()->status); + } + + public function test_update_status_validates_status_value(): void + { + $this->actingAs($this->apoteker); + $resep = $this->createResep(['status' => 'proses']); + + $response = $this->patch(route('resep.update-status', $resep), [ + 'status' => 'invalid-status', + ]); + + $response->assertSessionHasErrors('status'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_resep_items_and_restores_stok_if_selesai(): void + { + $this->actingAs($this->dokter); + // Buat resep dengan status selesai + $resep = $this->createResep(['status' => 'selesai']); + + // Buat item resep dan kurangi stok (karena selesai) + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'jumlah' => 20, + ]); + $this->obatMasuk->decrement('stok', 20); + + // Buat obat keluar terkait + ObatKeluar::create([ + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'no_pengeluaran' => $resep->no_resep, + 'jumlah' => 20, + 'tujuan_pemakaian' => 'Resep test', + 'tanggal_pengeluaran' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'nama_petugas' => 'Petugas', + 'nama_penerima' => 'Pasien', + 'kode_batch' => 'BTH-001', + 'status' => 'selesai', + 'user_id' => $this->dokter->id, + ]); + + $response = $this->delete(route('resep.destroy', $resep)); + + $response->assertRedirect(route('resep.index')); + $response->assertSessionHas('success'); + + // Resep item dan resep terhapus + $this->assertDatabaseMissing('reseps', ['id' => $resep->id]); + $this->assertDatabaseMissing('resep_items', ['resep_id' => $resep->id]); + + // ObatKeluar terkait terhapus + $this->assertDatabaseMissing('obat_keluars', ['no_pengeluaran' => $resep->no_resep]); + + // Stok dikembalikan: 80 โ†’ 100 (karena statusnya selesai) + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + } + + public function test_destroy_does_not_restore_stok_if_proses(): void + { + $this->actingAs($this->dokter); + // Buat resep dengan status proses + $resep = $this->createResep(['status' => 'proses']); + + // Buat item resep (stok tidak dikurangi karena proses) + ResepItem::create([ + 'resep_id' => $resep->id, + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'jumlah' => 20, + ]); + + // Buat obat keluar terkait + ObatKeluar::create([ + 'obat_masuk_id' => $this->obatMasuk->id, + 'nama_obat' => 'Amoxicillin', + 'no_pengeluaran' => $resep->no_resep, + 'jumlah' => 20, + 'tujuan_pemakaian' => 'Resep test', + 'tanggal_pengeluaran' => now()->toDateString(), + 'tanggal_kadaluarsa' => now()->addYear()->toDateString(), + 'nama_petugas' => 'Petugas', + 'nama_penerima' => 'Pasien', + 'kode_batch' => 'BTH-001', + 'status' => 'proses', + 'user_id' => $this->dokter->id, + ]); + + $response = $this->delete(route('resep.destroy', $resep)); + + $response->assertRedirect(route('resep.index')); + $response->assertSessionHas('success'); + + // Stok tetap 100 karena belum pernah dikurangi (status proses) + $this->assertEquals(100, $this->obatMasuk->fresh()->stok); + } +} diff --git a/tests/Feature/SatuanTest.php b/tests/Feature/SatuanTest.php new file mode 100644 index 0000000..6af3a23 --- /dev/null +++ b/tests/Feature/SatuanTest.php @@ -0,0 +1,143 @@ +create(['role' => 'apoteker']); + $this->actingAs($user); + return $user; + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_returns_200_for_authenticated_user(): void + { + $this->actingAsUser(); + $response = $this->get(route('satuan.index')); + $response->assertStatus(200); + } + + public function test_index_redirects_guest(): void + { + $response = $this->get(route('satuan.index')); + $response->assertRedirect(route('login')); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // store + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_store_creates_satuan_with_valid_data(): void + { + $this->actingAsUser(); + + $response = $this->post(route('satuan.store'), [ + 'nama' => 'Tablet', + 'keterangan' => 'Satuan tablet', + ]); + + $response->assertRedirect(route('satuan.index')); + $response->assertSessionHas('success'); + $this->assertDatabaseHas('satuans', ['nama' => 'Tablet']); + } + + public function test_store_fails_without_nama(): void + { + $this->actingAsUser(); + + $response = $this->post(route('satuan.store'), ['nama' => '']); + $response->assertSessionHasErrors('nama'); + } + + public function test_store_fails_with_duplicate_nama(): void + { + $this->actingAsUser(); + Satuan::create(['nama' => 'Tablet']); + + $response = $this->post(route('satuan.store'), ['nama' => 'Tablet']); + $response->assertSessionHasErrors('nama'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // update + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_update_modifies_satuan(): void + { + $this->actingAsUser(); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $response = $this->put(route('satuan.update', $satuan), [ + 'nama' => 'Kapsul', + 'keterangan' => 'Satuan kapsul', + ]); + + $response->assertRedirect(route('satuan.index')); + $this->assertDatabaseHas('satuans', ['nama' => 'Kapsul']); + $this->assertDatabaseMissing('satuans', ['nama' => 'Tablet']); + } + + public function test_update_allows_same_nama_for_same_satuan(): void + { + $this->actingAsUser(); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $response = $this->put(route('satuan.update', $satuan), [ + 'nama' => 'Tablet', + 'keterangan' => 'Updated', + ]); + + $response->assertRedirect(route('satuan.index')); + $this->assertDatabaseHas('satuans', ['id' => $satuan->id, 'nama' => 'Tablet']); + } + + public function test_update_fails_with_duplicate_nama_of_other_satuan(): void + { + $this->actingAsUser(); + Satuan::create(['nama' => 'Kapsul']); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $response = $this->put(route('satuan.update', $satuan), [ + 'nama' => 'Kapsul', + ]); + + $response->assertSessionHasErrors('nama'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_satuan(): void + { + $this->actingAsUser(); + $satuan = Satuan::create(['nama' => 'Tablet']); + + $response = $this->delete(route('satuan.destroy', $satuan)); + + $response->assertRedirect(route('satuan.index')); + $this->assertDatabaseMissing('satuans', ['id' => $satuan->id]); + } + + public function test_superadmin_can_crud_satuan(): void + { + $superadmin = User::factory()->create(['role' => 'superadmin']); + $this->actingAs($superadmin); + + $this->get(route('satuan.create'))->assertStatus(200); + $this->post(route('satuan.store'), ['nama' => 'Botol'])->assertRedirect(route('satuan.index')); + $satuan = Satuan::where('nama', 'Botol')->firstOrFail(); + $this->get(route('satuan.edit', $satuan))->assertStatus(200); + $this->put(route('satuan.update', $satuan), ['nama' => 'Botol Update'])->assertRedirect(route('satuan.index')); + $this->delete(route('satuan.destroy', $satuan))->assertRedirect(route('satuan.index')); + } +} diff --git a/tests/Feature/UserManagementTest.php b/tests/Feature/UserManagementTest.php new file mode 100644 index 0000000..f2d7cdd --- /dev/null +++ b/tests/Feature/UserManagementTest.php @@ -0,0 +1,329 @@ +superadmin = User::factory()->create(['role' => 'superadmin']); + $this->dokter = User::factory()->create(['role' => 'dokter', 'name' => 'Dr. Budi']); + $this->apoteker = User::factory()->create(['role' => 'apoteker', 'name' => 'Apt. Siti']); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Akses: hanya superadmin + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_superadmin_can_access_index(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.index')); + $response->assertStatus(200); + } + + public function test_dokter_cannot_access_index(): void + { + $this->actingAs($this->dokter); + $response = $this->get(route('user-management.index')); + $response->assertStatus(403); + } + + public function test_apoteker_cannot_access_index(): void + { + $this->actingAs($this->apoteker); + $response = $this->get(route('user-management.index')); + $response->assertStatus(403); + } + + public function test_guest_is_redirected_to_login(): void + { + $response = $this->get(route('user-management.index')); + $response->assertRedirect(route('login')); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // index โ€” filter & search + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_index_does_not_show_superadmin_users(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.index')); + $response->assertDontSee($this->superadmin->email); + $response->assertSee('Dr. Budi'); + } + + public function test_index_search_by_name(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.index', ['search' => 'Budi'])); + $response->assertSee('Dr. Budi'); + $response->assertDontSee('Apt. Siti'); + } + + public function test_index_filter_by_role(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.index', ['role' => 'dokter'])); + $response->assertSee('Dr. Budi'); + $response->assertDontSee('Apt. Siti'); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // create & store + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_superadmin_can_access_create_page(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.create')); + $response->assertStatus(200); + } + + public function test_store_creates_new_dokter(): void + { + $this->actingAs($this->superadmin); + + $response = $this->post(route('user-management.store'), [ + 'name' => 'Dr. Andi', + 'email' => 'andi@example.com', + 'role' => 'dokter', + 'password' => 'password123', + 'password_confirmation' => 'password123', + ]); + + $response->assertRedirect(route('user-management.index')); + $response->assertSessionHas('success'); + $this->assertDatabaseHas('users', [ + 'email' => 'andi@example.com', + 'role' => 'dokter', + ]); + } + + public function test_store_creates_new_apoteker(): void + { + $this->actingAs($this->superadmin); + + $response = $this->post(route('user-management.store'), [ + 'name' => 'Apt. Rina', + 'email' => 'rina@example.com', + 'role' => 'apoteker', + 'password' => 'password123', + 'password_confirmation' => 'password123', + ]); + + $response->assertRedirect(route('user-management.index')); + $this->assertDatabaseHas('users', [ + 'email' => 'rina@example.com', + 'role' => 'apoteker', + ]); + } + + public function test_store_fails_without_required_fields(): void + { + $this->actingAs($this->superadmin); + + $response = $this->post(route('user-management.store'), []); + $response->assertSessionHasErrors(['name', 'email', 'role', 'password']); + } + + public function test_store_fails_with_duplicate_email(): void + { + $this->actingAs($this->superadmin); + + $response = $this->post(route('user-management.store'), [ + 'name' => 'Duplicate', + 'email' => $this->dokter->email, + 'role' => 'dokter', + 'password' => 'password123', + 'password_confirmation' => 'password123', + ]); + + $response->assertSessionHasErrors('email'); + } + + public function test_store_cannot_create_superadmin_role(): void + { + $this->actingAs($this->superadmin); + + $response = $this->post(route('user-management.store'), [ + 'name' => 'Fake Admin', + 'email' => 'fake@example.com', + 'role' => 'superadmin', + 'password' => 'password123', + 'password_confirmation' => 'password123', + ]); + + $response->assertSessionHasErrors('role'); + $this->assertDatabaseMissing('users', ['email' => 'fake@example.com']); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // edit & update + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_superadmin_can_access_edit_page(): void + { + $this->actingAs($this->superadmin); + $response = $this->get(route('user-management.edit', $this->dokter)); + $response->assertStatus(200); + } + + public function test_edit_superadmin_user_returns_403(): void + { + $this->actingAs($this->superadmin); + $otherAdmin = User::factory()->create(['role' => 'superadmin']); + + $response = $this->get(route('user-management.edit', $otherAdmin)); + $response->assertStatus(403); + } + + public function test_update_changes_user_data(): void + { + $this->actingAs($this->superadmin); + + $response = $this->put(route('user-management.update', $this->dokter), [ + 'name' => 'Dr. Budi Updated', + 'email' => $this->dokter->email, + 'role' => 'dokter', + ]); + + $response->assertRedirect(route('user-management.index')); + $response->assertSessionHas('success'); + $this->assertEquals('Dr. Budi Updated', $this->dokter->fresh()->name); + } + + public function test_update_can_change_role(): void + { + $this->actingAs($this->superadmin); + + $response = $this->put(route('user-management.update', $this->dokter), [ + 'name' => $this->dokter->name, + 'email' => $this->dokter->email, + 'role' => 'apoteker', + ]); + + $response->assertRedirect(route('user-management.index')); + $this->assertEquals('apoteker', $this->dokter->fresh()->role); + } + + public function test_update_without_password_keeps_old_password(): void + { + $this->actingAs($this->superadmin); + $oldPassword = $this->dokter->password; + + $this->put(route('user-management.update', $this->dokter), [ + 'name' => $this->dokter->name, + 'email' => $this->dokter->email, + 'role' => 'dokter', + ]); + + $this->assertEquals($oldPassword, $this->dokter->fresh()->password); + } + + public function test_update_with_new_password_changes_password(): void + { + $this->actingAs($this->superadmin); + $oldPassword = $this->dokter->password; + + $this->put(route('user-management.update', $this->dokter), [ + 'name' => $this->dokter->name, + 'email' => $this->dokter->email, + 'role' => 'dokter', + 'password' => 'newpassword123', + 'password_confirmation' => 'newpassword123', + ]); + + $this->assertNotEquals($oldPassword, $this->dokter->fresh()->password); + } + + public function test_update_superadmin_user_returns_403(): void + { + $this->actingAs($this->superadmin); + $otherAdmin = User::factory()->create(['role' => 'superadmin']); + + $response = $this->put(route('user-management.update', $otherAdmin), [ + 'name' => 'Hacked', + 'email' => $otherAdmin->email, + 'role' => 'dokter', + ]); + + $response->assertStatus(403); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_destroy_deletes_user(): void + { + $this->actingAs($this->superadmin); + $userId = $this->dokter->id; + + $response = $this->delete(route('user-management.destroy', $this->dokter)); + + $response->assertRedirect(route('user-management.index')); + $response->assertSessionHas('success'); + $this->assertDatabaseMissing('users', ['id' => $userId]); + } + + public function test_destroy_cannot_delete_superadmin(): void + { + $this->actingAs($this->superadmin); + $otherAdmin = User::factory()->create(['role' => 'superadmin']); + + $response = $this->delete(route('user-management.destroy', $otherAdmin)); + $response->assertStatus(403); + $this->assertDatabaseHas('users', ['id' => $otherAdmin->id]); + } + + public function test_destroy_cannot_delete_self(): void + { + $this->actingAs($this->superadmin); + + $response = $this->delete(route('user-management.destroy', $this->superadmin)); + + $response->assertRedirect(route('user-management.index')); + $response->assertSessionHas('error'); + $this->assertDatabaseHas('users', ['id' => $this->superadmin->id]); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Non-superadmin cannot store/update/destroy + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_dokter_cannot_store_user(): void + { + $this->actingAs($this->dokter); + + $response = $this->post(route('user-management.store'), [ + 'name' => 'Unauthorized', + 'email' => 'unauth@example.com', + 'role' => 'dokter', + 'password' => 'password123', + 'password_confirmation' => 'password123', + ]); + + $response->assertStatus(403); + $this->assertDatabaseMissing('users', ['email' => 'unauth@example.com']); + } + + public function test_apoteker_cannot_delete_user(): void + { + $this->actingAs($this->apoteker); + + $response = $this->delete(route('user-management.destroy', $this->dokter)); + $response->assertStatus(403); + $this->assertDatabaseHas('users', ['id' => $this->dokter->id]); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +} diff --git a/tests/Unit/ObatKeluarModelTest.php b/tests/Unit/ObatKeluarModelTest.php new file mode 100644 index 0000000..6d80999 --- /dev/null +++ b/tests/Unit/ObatKeluarModelTest.php @@ -0,0 +1,94 @@ +setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(30)->toDateString()]); + + $this->assertGreaterThan(0, $obat->sisa_hari); + } + + public function test_sisa_hari_is_negative_for_expired(): void + { + $obat = new ObatKeluar(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->subDays(10)->toDateString()]); + + $this->assertLessThan(0, $obat->sisa_hari); + } + + public function test_sisa_hari_returns_integer(): void + { + $obat = new ObatKeluar(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(60)->toDateString()]); + + $this->assertIsInt($obat->sisa_hari); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Fillable & Casts + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_obat_keluar_has_correct_fillable_fields(): void + { + $obat = new ObatKeluar(); + $fillable = $obat->getFillable(); + + $this->assertContains('obat_masuk_id', $fillable); + $this->assertContains('nama_obat', $fillable); + $this->assertContains('jumlah', $fillable); + $this->assertContains('status', $fillable); + $this->assertContains('nama_petugas', $fillable); + $this->assertContains('nama_penerima', $fillable); + $this->assertContains('tujuan_pemakaian', $fillable); + } + + public function test_tanggal_columns_are_cast_as_date(): void + { + $obat = new ObatKeluar(); + $casts = $obat->getCasts(); + $this->assertEquals('date', $casts['tanggal_pengeluaran']); + $this->assertEquals('date', $casts['tanggal_kadaluarsa']); + } + + public function test_harga_columns_are_cast_as_decimal(): void + { + $obat = new ObatKeluar(); + $casts = $obat->getCasts(); + $this->assertArrayHasKey('harga', $casts); + $this->assertArrayHasKey('harga_total', $casts); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Relationships + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_obat_keluar_belongs_to_obat_masuk(): void + { + $obat = new ObatKeluar(); + $this->assertInstanceOf( + \Illuminate\Database\Eloquent\Relations\BelongsTo::class, + $obat->obatMasuk() + ); + } + + public function test_obat_keluar_belongs_to_user(): void + { + $obat = new ObatKeluar(); + $this->assertInstanceOf( + \Illuminate\Database\Eloquent\Relations\BelongsTo::class, + $obat->user() + ); + } +} diff --git a/tests/Unit/ObatMasukModelTest.php b/tests/Unit/ObatMasukModelTest.php new file mode 100644 index 0000000..0861c48 --- /dev/null +++ b/tests/Unit/ObatMasukModelTest.php @@ -0,0 +1,143 @@ +tanggal_kadaluarsa = Carbon::now()->addDays(30)->toDateString(); + + // Cast manually to trigger accessor + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(30)->toDateString()]); + + $this->assertGreaterThan(0, $obat->sisa_hari); + } + + public function test_sisa_hari_is_negative_for_past_expiry(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->subDays(5)->toDateString()]); + + $this->assertLessThan(0, $obat->sisa_hari); + } + + public function test_sisa_hari_is_zero_or_very_small_for_today_expiry(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->toDateString()]); + + // Bisa 0 atau 1 tergantung waktu persis + $this->assertLessThanOrEqual(1, abs($obat->sisa_hari)); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // getStatusKadaluarsaAttribute() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_status_kadaluarsa_is_kritis_when_less_than_or_equal_30_days(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(25)->toDateString()]); + + $this->assertEquals('kritis', $obat->status_kadaluarsa); + } + + public function test_status_kadaluarsa_is_kritis_when_already_expired(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->subDays(1)->toDateString()]); + + $this->assertEquals('kritis', $obat->status_kadaluarsa); + } + + public function test_status_kadaluarsa_is_kritis_when_exactly_30_days(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(30)->toDateString()]); + + $this->assertEquals('kritis', $obat->status_kadaluarsa); + } + + public function test_status_kadaluarsa_is_waspada_when_between_31_and_60_days(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(45)->toDateString()]); + + $this->assertEquals('waspada', $obat->status_kadaluarsa); + } + + public function test_status_kadaluarsa_is_aman_when_more_than_60_days(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(90)->toDateString()]); + + $this->assertEquals('aman', $obat->status_kadaluarsa); + } + + public function test_status_kadaluarsa_is_aman_when_more_than_60_days_away(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['tanggal_kadaluarsa' => Carbon::now()->addDays(65)->toDateString()]); + + $this->assertEquals('aman', $obat->status_kadaluarsa); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // getStatusStokAttribute() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_status_stok_is_tersedia_when_stok_greater_than_zero(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['stok' => 10]); + + $this->assertEquals('tersedia', $obat->status_stok); + } + + public function test_status_stok_is_habis_when_stok_is_zero(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['stok' => 0]); + + $this->assertEquals('habis', $obat->status_stok); + } + + public function test_status_stok_is_tersedia_when_stok_is_one(): void + { + $obat = new ObatMasuk(); + $obat->setRawAttributes(['stok' => 1]); + + $this->assertEquals('tersedia', $obat->status_stok); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Fillable & Casts + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_obat_masuk_has_correct_fillable_fields(): void + { + $obat = new ObatMasuk(); + $this->assertContains('nama_obat', $obat->getFillable()); + $this->assertContains('kode_batch', $obat->getFillable()); + $this->assertContains('stok', $obat->getFillable()); + $this->assertContains('tanggal_kadaluarsa', $obat->getFillable()); + } + + public function test_tanggal_columns_are_cast_as_date(): void + { + $obat = new ObatMasuk(); + $casts = $obat->getCasts(); + $this->assertEquals('date', $casts['tanggal_penerimaan']); + $this->assertEquals('date', $casts['tanggal_kadaluarsa']); + } +} diff --git a/tests/Unit/ResepModelTest.php b/tests/Unit/ResepModelTest.php new file mode 100644 index 0000000..42b4ac3 --- /dev/null +++ b/tests/Unit/ResepModelTest.php @@ -0,0 +1,232 @@ + 'selesai']); + $this->assertEquals('terkirim', $resep->status_color); + } + + public function test_status_color_is_proses_when_status_proses(): void + { + $resep = new Resep(['status' => 'proses']); + $this->assertEquals('proses', $resep->status_color); + } + + public function test_status_color_is_dibatalkan_when_status_dibatalkan(): void + { + $resep = new Resep(['status' => 'dibatalkan']); + $this->assertEquals('dibatalkan', $resep->status_color); + } + + public function test_status_color_defaults_to_proses_for_unknown_status(): void + { + $resep = new Resep(['status' => 'unknown']); + $this->assertEquals('proses', $resep->status_color); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // generateNoResep() - requires DB + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_generate_no_resep_starts_with_rsp_prefix(): void + { + $noResep = Resep::generateNoResep(); + $this->assertStringStartsWith('RSP-', $noResep); + } + + public function test_generate_no_resep_contains_today_date(): void + { + $today = now()->format('Ymd'); + $noResep = Resep::generateNoResep(); + $this->assertStringContainsString($today, $noResep); + } + + public function test_generate_no_resep_starts_at_0001_when_no_resep_today(): void + { + // Tidak ada resep hari ini + $today = now()->format('Ymd'); + $noResep = Resep::generateNoResep(); + $this->assertEquals("RSP-{$today}-0001", $noResep); + } + + public function test_generate_no_resep_increments_when_resep_exists_today(): void + { + $user = User::factory()->create(); + + // Buat resep pertama hari ini + Resep::create([ + 'no_resep' => Resep::generateNoResep(), + 'user_id' => $user->id, + 'nama_pasien' => 'Pasien A', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Flu', + 'status' => 'proses', + 'nama_dokter' => 'Dokter A', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'L', + 'umur_pasien' => 30, + 'berat_badan' => 60, + 'is_read' => false, + ]); + + $today = now()->format('Ymd'); + $noResep = Resep::generateNoResep(); + $this->assertEquals("RSP-{$today}-0002", $noResep); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // scopeUnread() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_scope_unread_only_returns_unread_reseps(): void + { + $user = User::factory()->create(); + + Resep::create([ + 'no_resep' => 'RSP-TEST-0001', + 'user_id' => $user->id, + 'nama_pasien' => 'Pasien Unread', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Flu', + 'status' => 'proses', + 'nama_dokter' => 'Dokter A', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'L', + 'umur_pasien' => 25, + 'berat_badan' => 55, + 'is_read' => false, + ]); + + Resep::create([ + 'no_resep' => 'RSP-TEST-0002', + 'user_id' => $user->id, + 'nama_pasien' => 'Pasien Read', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Batuk', + 'status' => 'proses', + 'nama_dokter' => 'Dokter B', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'P', + 'umur_pasien' => 30, + 'berat_badan' => 50, + 'is_read' => true, + ]); + + $unread = Resep::unread()->get(); + $this->assertCount(1, $unread); + $this->assertEquals('Pasien Unread', $unread->first()->nama_pasien); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // markAsRead() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_mark_as_read_sets_is_read_to_true(): void + { + $user = User::factory()->create(); + + $resep = Resep::create([ + 'no_resep' => 'RSP-TEST-0010', + 'user_id' => $user->id, + 'nama_pasien' => 'Pasien Test', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Flu', + 'status' => 'proses', + 'nama_dokter' => 'Dokter X', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'L', + 'umur_pasien' => 25, + 'berat_badan' => 60, + 'is_read' => false, + ]); + + $resep->markAsRead(); + $resep->refresh(); + + $this->assertTrue($resep->is_read); + } + + public function test_mark_as_read_does_not_re_update_if_already_read(): void + { + $user = User::factory()->create(); + + $resep = Resep::create([ + 'no_resep' => 'RSP-TEST-0011', + 'user_id' => $user->id, + 'nama_pasien' => 'Pasien Read', + 'tanggal_resep' => now()->toDateString(), + 'diagnosa' => 'Flu', + 'status' => 'proses', + 'nama_dokter' => 'Dokter Y', + 'jenis_penjamin' => 'umum', + 'jenis_layanan' => 'BP', + 'jenis_kelamin' => 'P', + 'umur_pasien' => 20, + 'berat_badan' => 45, + 'is_read' => true, + ]); + + // Panggil markAsRead() ketika sudah true โ€” tidak boleh error + $resep->markAsRead(); + $resep->refresh(); + + $this->assertTrue($resep->is_read); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Relationships + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_resep_belongs_to_user(): void + { + $resep = new Resep(); + $this->assertInstanceOf( + \Illuminate\Database\Eloquent\Relations\BelongsTo::class, + $resep->user() + ); + } + + public function test_resep_has_many_items(): void + { + $resep = new Resep(); + $this->assertInstanceOf( + \Illuminate\Database\Eloquent\Relations\HasMany::class, + $resep->items() + ); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Fillable & Casts + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_resep_is_read_is_cast_as_boolean(): void + { + $resep = new Resep(); + $casts = $resep->getCasts(); + $this->assertEquals('boolean', $casts['is_read']); + } + + public function test_resep_tanggal_resep_is_cast_as_date(): void + { + $resep = new Resep(); + $casts = $resep->getCasts(); + $this->assertEquals('date', $casts['tanggal_resep']); + } +} diff --git a/tests/Unit/UserModelTest.php b/tests/Unit/UserModelTest.php new file mode 100644 index 0000000..791abe3 --- /dev/null +++ b/tests/Unit/UserModelTest.php @@ -0,0 +1,101 @@ +create(['role' => 'dokter']); + $this->assertTrue($user->isDokter()); + } + + public function test_is_dokter_returns_false_when_role_is_not_dokter(): void + { + $user = User::factory()->create(['role' => 'apoteker']); + $this->assertFalse($user->isDokter()); + } + + public function test_is_dokter_returns_false_when_role_is_empty(): void + { + $user = User::factory()->create(['role' => null]); + $this->assertFalse($user->isDokter()); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // isApoteker() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_is_apoteker_returns_true_when_role_is_apoteker(): void + { + $user = User::factory()->create(['role' => 'apoteker']); + $this->assertTrue($user->isApoteker()); + } + + public function test_is_apoteker_returns_false_when_role_is_dokter(): void + { + $user = User::factory()->create(['role' => 'dokter']); + $this->assertFalse($user->isApoteker()); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // canManageResep() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_can_manage_resep_returns_true_for_dokter(): void + { + $user = User::factory()->create(['role' => 'dokter']); + $this->assertTrue($user->canManageResep()); + } + + public function test_can_manage_resep_returns_false_for_apoteker(): void + { + $user = User::factory()->create(['role' => 'apoteker']); + $this->assertFalse($user->canManageResep()); + } + + public function test_can_manage_resep_returns_false_for_admin(): void + { + $user = User::factory()->create(['role' => 'admin']); + $this->assertFalse($user->canManageResep()); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Relasi reseps() + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_user_has_many_reseps_relationship(): void + { + $user = User::factory()->create(['role' => 'dokter']); + $this->assertInstanceOf( + \Illuminate\Database\Eloquent\Relations\HasMany::class, + $user->reseps() + ); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Fillable / Hidden attributes + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + public function test_password_is_hidden_from_serialization(): void + { + $user = User::factory()->create(['password' => bcrypt('secret')]); + $array = $user->toArray(); + $this->assertArrayNotHasKey('password', $array); + } + + public function test_user_fillable_fields_are_correct(): void + { + $user = new User(); + $expected = ['name', 'nip', 'email', 'phone', 'gender', 'address', 'division', 'position', 'profile_photo', 'password', 'role']; + $this->assertEquals($expected, $user->getFillable()); + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..421b569 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + }), + ], +});