first commit

This commit is contained in:
Elsaaaa13 2026-06-15 14:12:34 +07:00
commit f01cfe7152
232 changed files with 30665 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -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

64
.env Normal file
View File

@ -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}"

65
.env.example Normal file
View File

@ -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}"

11
.gitattributes vendored Normal file
View File

@ -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

23
.gitignore vendored Normal file
View File

@ -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

213
README.md Normal file
View File

@ -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 <repository-url>
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.

View File

@ -0,0 +1,55 @@
<?php
namespace App\Exports;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ObatKeluarExport implements FromCollection, WithHeadings, WithMapping
{
protected $startDate;
protected $endDate;
public function __construct($startDate, $endDate)
{
$this->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,
];
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Exports;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ObatMasukExport implements FromCollection, WithHeadings, WithMapping
{
protected $startDate;
protected $endDate;
public function __construct($startDate, $endDate)
{
$this->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,
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): View
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->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('/');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*/
public function show(): View
{
return view('auth.confirm-password');
}
/**
* Confirm the user's password.
*/
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('web')->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));
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*/
public function __invoke(Request $request): RedirectResponse|View
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: view('auth.verify-email');
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $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)]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class PasswordController extends Controller
{
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->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');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\View\View;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): View
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->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)]);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->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));
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->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');
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers;
use App\Models\Obat;
use App\Models\ObatMasuk;
use App\Models\ObatKeluar;
use Carbon\Carbon;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index(Request $request)
{
// Get filter month and year for chart (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);
// 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'
));
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use App\Models\ObatMasuk;
use Illuminate\Http\Request;
class KadaluarsaController extends Controller
{
public function index(Request $request)
{
// Only show medicines that are expired (minus days) or expiring within 60 days (awas/waspada)
$query = ObatMasuk::with(['obat.kategori'])
->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'));
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use App\Models\Kategori;
use Illuminate\Http\Request;
class KategoriController extends Controller
{
public function index()
{
$kategoris = Kategori::orderBy('nama')->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');
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Http\Controllers;
use App\Models\ObatMasuk;
use App\Models\ObatKeluar;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Barryvdh\DomPDF\Facade\Pdf;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\ObatMasukExport;
use App\Exports\ObatKeluarExport;
class LaporanController extends Controller
{
public function index(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'));
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);
}
}
}

View File

@ -0,0 +1,216 @@
<?php
namespace App\Http\Controllers;
use App\Models\ObatMasuk;
use App\Models\ObatKeluar;
use App\Models\Kategori;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class ObatKeluarController extends Controller
{
public function index(Request $request)
{
$query = ObatKeluar::with(['obatMasuk.kategori']);
// Search
if ($request->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');
}
}

View File

@ -0,0 +1,166 @@
<?php
namespace App\Http\Controllers;
use App\Models\ObatMasuk;
use App\Models\Kategori;
use App\Models\Satuan;
use App\Models\Supplier;
use Illuminate\Http\Request;
use Carbon\Carbon;
class ObatMasukController extends Controller
{
public function index(Request $request)
{
$query = ObatMasuk::query();
// Search
if ($request->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');
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $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('/');
}
}

View File

@ -0,0 +1,382 @@
<?php
namespace App\Http\Controllers;
use App\Models\Resep;
use App\Models\ResepItem;
use App\Models\ObatMasuk;
use App\Models\ObatKeluar;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ResepController extends Controller
{
/**
* Display a listing of the prescriptions.
*/
public function index(Request $request)
{
$query = Resep::with(['user', 'items']);
// Search
if ($request->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'));
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use App\Models\Satuan;
use Illuminate\Http\Request;
class SatuanController extends Controller
{
public function index()
{
$satuans = Satuan::orderBy('nama')->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');
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
class UserManagementController extends Controller
{
/**
* Display a listing of users (dokter accounts).
*/
public function index(Request $request)
{
$query = User::where('role', '!=', 'superadmin');
if ($request->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.');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string ...$roles
*/
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!$request->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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());
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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'],
];
}
}

16
app/Models/Kategori.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Kategori extends Model
{
protected $fillable = ['nama', 'keterangan'];
public function obats(): HasMany
{
return $this->hasMany(Obat::class);
}
}

34
app/Models/Obat.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Obat extends Model
{
protected $fillable = ['kategori_id', 'nama', 'kode', 'satuan', 'deskripsi'];
public function kategori(): BelongsTo
{
return $this->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;
}
}

52
app/Models/ObatKeluar.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Carbon\Carbon;
class ObatKeluar extends Model
{
protected $fillable = [
'obat_masuk_id',
'nama_obat',
'sumber_dana',
'user_id',
'kode_batch',
'barcode',
'jumlah',
'harga',
'harga_total',
'tujuan_pemakaian',
'tanggal_pengeluaran',
'tanggal_kadaluarsa',
'no_pengeluaran',
'nama_petugas',
'nama_penerima',
'catatan',
'status',
];
protected $casts = [
'tanggal_pengeluaran' => '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);
}
}

83
app/Models/ObatMasuk.php Normal file
View File

@ -0,0 +1,83 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Carbon\Carbon;
class ObatMasuk extends Model
{
protected $fillable = [
'nama_obat',
'kategori_id',
'satuan_id',
'obat_id',
'sumber_dana',
'user_id',
'kode_batch',
'barcode',
'stok',
'harga_beli',
'harga_jual',
'tanggal_penerimaan',
'tanggal_kadaluarsa',
'no_faktur',
'no_sbbk',
'catatan',
];
protected $casts = [
'tanggal_penerimaan' => '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';
}
}

113
app/Models/Resep.php Normal file
View File

@ -0,0 +1,113 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
class Resep extends Model
{
protected $fillable = [
'no_resep',
'user_id',
'nama_dokter',
'no_sip',
'jenis_penjamin',
'jenis_layanan',
'nama_pasien',
'no_rm',
'umur_pasien',
'berat_badan',
'alamat_pasien',
'jenis_kelamin',
'tanggal_resep',
'diagnosa',
'catatan',
'status',
'is_read',
];
protected $casts = [
'tanggal_resep' => '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]);
}
}
}

33
app/Models/ResepItem.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ResepItem extends Model
{
protected $fillable = [
'resep_id',
'obat_masuk_id',
'nama_obat',
'jumlah',
'aturan_pakai',
];
/**
* Relasi ke Resep
*/
public function resep(): BelongsTo
{
return $this->belongsTo(Resep::class);
}
/**
* Relasi ke ObatMasuk
*/
public function obatMasuk(): BelongsTo
{
return $this->belongsTo(ObatMasuk::class);
}
}

10
app/Models/Satuan.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Satuan extends Model
{
protected $fillable = ['nama', 'keterangan'];
}

16
app/Models/Supplier.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Supplier extends Model
{
protected $fillable = ['nama', 'alamat', 'telepon', 'email'];
public function obatMasuks(): HasMany
{
return $this->hasMany(ObatMasuk::class);
}
}

97
app/Models/User.php Normal file
View File

@ -0,0 +1,97 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'nip',
'email',
'phone',
'gender',
'address',
'division',
'position',
'profile_photo',
'password',
'role',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
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);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Providers;
use App\Models\Resep;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Kustomisasi Email Reset Password
ResetPassword::toMailUsing(function (object $notifiable, string $token) {
return (new MailMessage)
->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);
});
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.app');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.guest');
}
}

18
artisan Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

20
bootstrap/app.php Normal file
View File

@ -0,0 +1,20 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->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();

2
bootstrap/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

5
bootstrap/providers.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

89
composer.json Normal file
View File

@ -0,0 +1,89 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"maatwebsite/excel": "^3.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/breeze": "^2.3",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

9397
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

126
config/app.php Normal file
View File

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => 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'),
],
];

115
config/auth.php Normal file
View File

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'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),
];

117
config/cache.php Normal file
View File

@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => 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-'),
];

183
config/database.php Normal file
View File

@ -0,0 +1,183 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => 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),
],
],
];

80
config/filesystems.php Normal file
View File

@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => 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'),
],
];

132
config/logging.php Normal file
View File

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => 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'),
],
],
];

118
config/mail.php Normal file
View File

@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => 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'),
],
];

129
config/queue.php Normal file
View File

@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => 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',
],
];

38
config/services.php Normal file
View File

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'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'),
],
],
];

217
config/session.php Normal file
View File

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => 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),
];

1
database/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
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,
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('kategoris', function (Blueprint $table) {
$table->id();
$table->string('nama');
$table->string('keterangan')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('kategoris');
}
};

View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('suppliers', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('obats', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('obat_masuks', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('obat_keluars', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('satuans', function (Blueprint $table) {
$table->id();
$table->string('nama');
$table->string('keterangan')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('satuans');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('obat_masuks', function (Blueprint $table) {
$table->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']);
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_masuks', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_keluars', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_keluars', function (Blueprint $table) {
$table->string('tujuan_pemakaian')->nullable()->after('jumlah');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('obat_keluars', function (Blueprint $table) {
$table->dropColumn('tujuan_pemakaian');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_keluars', function (Blueprint $table) {
$table->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();
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['dokter', 'apoteker'])->default('apoteker')->after('email');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('reseps', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('resep_items', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('reseps', function (Blueprint $table) {
// Data Resep
$table->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',
]);
});
}
};

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_masuks', function (Blueprint $table) {
// Rename nama_supplier to sumber_dana
$table->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');
});
}
};

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('obat_keluars', function (Blueprint $table) {
// Add new fields with existence checks
if (!Schema::hasColumn('obat_keluars', 'barcode')) {
$table->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']);
});
}
};

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('obat_masuks', function (Blueprint $table) {
$table->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();
});
}
};

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->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',
]);
});
}
};

View File

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Change enum to include superadmin
DB::statement("ALTER TABLE users MODIFY COLUMN role ENUM('dokter', 'apoteker', 'superadmin') DEFAULT 'apoteker'");
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::statement("ALTER TABLE users MODIFY COLUMN role ENUM('dokter', 'apoteker') DEFAULT 'apoteker'");
}
};

View File

@ -0,0 +1,162 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* SQLite-compatible schema untuk testing.
* Menggabungkan semua tabel dalam satu migration.
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,126 @@
<?php
namespace Database\Seeders;
use App\Models\Kategori;
use App\Models\Satuan;
use App\Models\Supplier;
use App\Models\Obat;
use App\Models\ObatMasuk;
use App\Models\ObatKeluar;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Carbon\Carbon;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Create superadmin user
$this->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);
}
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
class SuperAdminSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::firstOrCreate(
['email' => 'superadmin@meddata.com'],
[
'name' => 'Super Admin',
'password' => Hash::make('password'),
'role' => 'superadmin',
]
);
}
}

732
medorymy_main new db.sql Normal file
View File

@ -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 */;

5
migration_error.txt Normal file
View File

@ -0,0 +1,5 @@

INFO Running migrations.
2026_02_08_000003_update_obat_keluars_table 295.04ms DONE

3428
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -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"
}
}

584
pencatatanobat2.sql Normal file
View File

@ -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 */;

35
phpunit.xml Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

25
public/.htaccess Normal file
View File

@ -0,0 +1,25 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
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]
</IfModule>

BIN
public/build (6).zip Normal file

Binary file not shown.

BIN
public/build.zip Normal file

Binary file not shown.

13
public/clear-cache.php Normal file
View File

@ -0,0 +1,13 @@
<?php
// Helper untuk clear Blade view cache di hosting
// HAPUS FILE INI setelah digunakan!
$viewCachePath = __DIR__ . '/../storage/framework/views/';
$files = glob($viewCachePath . '*.php');
$count = 0;
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
$count++;
}
}
echo "✓ Berhasil menghapus {$count} file view cache. Silakan hapus file clear-cache.php ini dari server.";

0
public/favicon.ico Normal file
View File

20
public/index.php Normal file
View File

@ -0,0 +1,20 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// Register the Composer autoloader...
require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request...
/** @var Application $app */
$app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

Some files were not shown because too many files have changed in this diff Show More