PART 1: Setup
bashcomposer create-project laravel/laravel medicine-system
cd medicine-system
Create database in phpMyAdmin:
medicine_db
Edit .env:
DB_DATABASE=medicine_db
DB_USERNAME=root
DB_PASSWORD=
Install Breeze:
bashcomposer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
npm install
npm run build
Install Bootstrap UI and PDF only:
bashnpm uninstall tailwindcss postcss autoprefixer
composer require aldhix/breeze-bootstrap-ui
php artisan breeze-bootstrap-ui:install --force
composer require barryvdh/laravel-dompdf
npm install
npm run build
Create files:
bashphp artisan make:model Medicine -mcr
php artisan make:controller ReportController
PART 2: Migration
Open database/migrations/xxxx_create_medicines_table.php:
php<?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('medicines', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('generic_name');
$table->string('category');
$table->integer('quantity');
$table->date('expiration_date');
$table->decimal('price', 8, 2);
$table->string('status')->default('available');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('medicines');
}
};
Run:
bashphp artisan migrate
PART 3: Model
Open app/Models/Medicine.php:
php<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Medicine extends Model
{
protected $fillable = [
'name',
'generic_name',
'category',
'quantity',
'expiration_date',
'price',
'status',
];
protected $casts = [
'expiration_date' => 'date',
];
public function isExpired(): bool
{
return $this->expiration_date->isPast();
}
public function isLowStock(): bool
{
return $this->quantity <= 10;
}
}
PART 4: Routes
Open routes/web.php and use this:
php<?php
use App\Http\Controllers\MedicineController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\ReportController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth')->group(function () {
Route::get('/', function () {
return redirect()->route('medicines.index');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
Route::resource('medicines', MedicineController::class);
Route::get('/reports', [ReportController::class, 'index'])->name('reports.index');
Route::get('/reports/pdf', [ReportController::class, 'exportPdf'])->name('reports.pdf');
Route::get('/reports/csv', [ReportController::class, 'exportCsv'])->name('reports.csv');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
require DIR.'/auth.php';
PART 5: Medicine Controller
Open app/Http/Controllers/MedicineController.php:
php<?php
namespace App\Http\Controllers;
use App\Models\Medicine;
use Illuminate\Http\Request;
class MedicineController extends Controller
{
public function index()
{
$medicines = Medicine::latest()->paginate(10);
return view('medicines.index', compact('medicines'));
}
public function create()
{
return view('medicines.create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'generic_name' => 'required|string|max:255',
'category' => 'required|string',
'quantity' => 'required|integer|min:0',
'expiration_date' => 'required|date',
'price' => 'required|numeric|min:0',
'status' => 'required|string',
]);
Medicine::create($request->all());
return redirect()->route('medicines.index')
->with('success', 'Medicine added successfully!');
}
public function show(Medicine $medicine)
{
return view('medicines.show', compact('medicine'));
}
public function edit(Medicine $medicine)
{
return view('medicines.edit', compact('medicine'));
}
public function update(Request $request, Medicine $medicine)
{
$request->validate([
'name' => 'required|string|max:255',
'generic_name' => 'required|string|max:255',
'category' => 'required|string',
'quantity' => 'required|integer|min:0',
'expiration_date' => 'required|date',
'price' => 'required|numeric|min:0',
'status' => 'required|string',
]);
$medicine->update($request->all());
return redirect()->route('medicines.index')
->with('success', 'Medicine updated successfully!');
}
public function destroy(Medicine $medicine)
{
$medicine->delete();
return redirect()->route('medicines.index')
->with('success', 'Medicine deleted successfully!');
}
}
PART 6: Report Controller
Open app/Http/Controllers/ReportController.php:
php<?php
namespace App\Http\Controllers;
use App\Models\Medicine;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
class ReportController extends Controller
{
public function index(Request $request)
{
$query = Medicine::query();
if ($request->filled('category')) {
$query->where('category', $request->category);
}
if ($request->filled('expiration_status')) {
if ($request->expiration_status === 'expired') {
$query->where('expiration_date', '<', now());
} elseif ($request->expiration_status === 'not_expired') {
$query->where('expiration_date', '>=', now());
}
}
$medicines = $query->get();
$categories = Medicine::distinct()->orderBy('category')->pluck('category');
return view('reports.index', compact('medicines', 'categories'));
}
public function exportPdf(Request $request)
{
$query = Medicine::query();
if ($request->filled('category')) {
$query->where('category', $request->category);
}
if ($request->filled('expiration_status')) {
if ($request->expiration_status === 'expired') {
$query->where('expiration_date', '<', now());
} elseif ($request->expiration_status === 'not_expired') {
$query->where('expiration_date', '>=', now());
}
}
$medicines = $query->get();
return Pdf::loadView('reports.pdf', compact('medicines'))
->download('medicine-report.pdf');
}
public function exportCsv(Request $request)
{
$query = Medicine::query();
if ($request->filled('category')) {
$query->where('category', $request->category);
}
if ($request->filled('expiration_status')) {
if ($request->expiration_status === 'expired') {
$query->where('expiration_date', '<', now());
} elseif ($request->expiration_status === 'not_expired') {
$query->where('expiration_date', '>=', now());
}
}
$medicines = $query->get();
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="medicine-report.csv"',
];
$callback = function () use ($medicines) {
$file = fopen('php://output', 'w');
fputcsv($file, ['Name', 'Generic Name', 'Category', 'Quantity', 'Expiration Date', 'Price', 'Status']);
foreach ($medicines as $medicine) {
fputcsv($file, [
$medicine->name,
$medicine->generic_name,
$medicine->category,
$medicine->quantity,
$medicine->expiration_date->format('Y-m-d'),
$medicine->price,
$medicine->status,
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}
PART 7: Layout Fix
Open resources/views/layouts/app.blade.php.
Find this:
html
{{ $slot }}
Replace with this:
html
@hasSection('content')
@yield('content')
@else
{{ $slot ?? '' }}
@endif
PART 8: Navigation
Open resources/views/layouts/navigation.blade.php.
Inside the left-side nav area, add or fix these links:
html
-
<li class="nav-item"> <x-nav-link :active="request()->routeIs('reports.*')" :href="route('reports.index')"> Reports </x-nav-link> </li>
PART 9: Blade Views
medicines/index.blade.php
Create/open resources/views/medicines/index.blade.php:
blade@extends('layouts.app')
@section('content')
<h2>Medicine Inventory</h2>
<a href="{{%20route('medicines.create')%20}}">+ Add Medicine</a>
@if(session('success'))
{{ session('success') }}
@endif
<table>
<thead>
<tr>
<th>Name</th>
<th>Generic Name</th>
<th>Category</th>
<th>Quantity</th>
<th>Expiration Date</th>
<th>Price</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($medicines as $medicine)
<tr>
<td>{{ $medicine->name }}</td>
<td>{{ $medicine->generic_name }}</td>
<td>{{ $medicine->category }}</td>
<td>{{ $medicine->quantity }}</td>
<td>{{ $medicine->expiration_date->format('M d, Y') }}</td>
<td>₱{{ number_format($medicine->price, 2) }}</td>
<td>
@if($medicine->status === 'available')
<span>Available</span>
@elseif($medicine->status === 'low_stock')
<span>Low Stock</span>
@else
<span>Out of Stock</span>
@endif
</td>
<td>
<a href="{{%20route('medicines.show',%20$medicine)%20}}">View</a>
<a href="{{%20route('medicines.edit',%20$medicine)%20}}">Edit</a>
@csrf
@method('DELETE')
Delete
</td>
</tr>
@empty
<tr>
<td colspan="8">No medicines yet. Add one!</td>
</tr>
@endforelse
</tbody>
</table>
{{ $medicines->links() }}
@endsection
medicines/create.blade.php
Create/open resources/views/medicines/create.blade.php:
blade@extends('layouts.app')
@section('content')
<h2>Add New Medicine</h2>
@if($errors->any())
@foreach($errors->all() as $error)
{{ $error }}
@endforeach
@endif
@csrf
Medicine Name
Generic Name
Category
-- Select Category --
@foreach(['Analgesic', 'Antibiotic', 'Antacid', 'Antihistamine', 'Vitamin', 'Other'] as $cat)
{{ $cat }}
@endforeach
Quantity
Expiration Date
Price (₱)
Status
Available
Low Stock
Out of Stock
Add Medicine
<a href="{{%20route('medicines.index')%20}}">Cancel</a>
@endsection
medicines/edit.blade.php
Create/open resources/views/medicines/edit.blade.php:
blade@extends('layouts.app')
@section('content')
<h2>Edit - {{ $medicine->name }}</h2>
@if($errors->any())
@foreach($errors->all() as $error)
{{ $error }}
@endforeach
@endif
@csrf
@method('PUT')
Medicine Name
Generic Name
Category
@foreach(['Analgesic', 'Antibiotic', 'Antacid', 'Antihistamine', 'Vitamin', 'Other'] as $cat)
category) == $cat ? 'selected' : '' }}>
{{ $cat }}
@endforeach
Quantity
Expiration Date
Price (₱)
Status
status) == 'available' ? 'selected' : '' }}>Available
status) == 'low_stock' ? 'selected' : '' }}>Low Stock
status) == 'out_of_stock' ? 'selected' : '' }}>Out of Stock
Update Medicine
<a href="{{%20route('medicines.index')%20}}">Cancel</a>
@endsection
medicines/show.blade.php
Create/open resources/views/medicines/show.blade.php:
blade@extends('layouts.app')
@section('content')
<h2>{{ $medicine->name }}</h2>
<p><strong>Generic Name:</strong> {{ $medicine->generic_name }}</p>
<p><strong>Category:</strong> {{ $medicine->category }}</p>
<p><strong>Quantity:</strong> {{ $medicine->quantity }}</p>
<p><strong>Expiration Date:</strong> {{ $medicine->expiration_date->format('F d, Y') }}</p>
<p><strong>Price:</strong> ₱{{ number_format($medicine->price, 2) }}</p>
<p>
<strong>Status:</strong>
@if($medicine->status === 'available')
<span>Available</span>
@elseif($medicine->status === 'low_stock')
<span>Low Stock</span>
@else
<span>Out of Stock</span>
@endif
</p>
<p><strong>Added:</strong> {{ $medicine->created_at->format('F d, Y') }}</p>
<a href="{{%20route('medicines.edit',%20$medicine)%20}}">Edit</a>
<a href="{{%20route('medicines.index')%20}}">Back</a>
@endsection
reports/index.blade.php
Create/open resources/views/reports/index.blade.php:
blade@extends('layouts.app')
@section('content')
<h2>Medicine Reports</h2>
Filter by Category
-- All Categories --
@foreach($categories as $cat)
{{ $cat }}
@endforeach
Filter by Expiration Status
-- All --
Not Expired
Expired
Filter
<a href="{{%20route('reports.index')%20}}">Reset</a>
<a href="{{%20route('reports.pdf',%20request()->query())%20}}">
Export PDF
</a>
<a href="{{%20route('reports.csv',%20request()->query())%20}}">
Export CSV
</a>
<p>Showing <strong>{{ $medicines->count() }}</strong> result(s)</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Generic Name</th>
<th>Category</th>
<th>Quantity</th>
<th>Expiration Date</th>
<th>Price</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@forelse($medicines as $medicine)
<tr>
<td>{{ $medicine->name }}</td>
<td>{{ $medicine->generic_name }}</td>
<td>{{ $medicine->category }}</td>
<td>{{ $medicine->quantity }}</td>
<td>
{{ $medicine->expiration_date->format('M d, Y') }}
@if($medicine->isExpired())
<span>Expired</span>
@endif
</td>
<td>₱{{ number_format($medicine->price, 2) }}</td>
<td>
@if($medicine->status === 'available')
<span>Available</span>
@elseif($medicine->status === 'low_stock')
<span>Low Stock</span>
@else
<span>Out of Stock</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="7">No results found.</td>
</tr>
@endforelse
</tbody>
</table>
@endsection
Top comments (0)