✅ Features Included
Inventory System Modules:
✔ Products
✔ Categories
✔ Suppliers
✔ Purchases
✔ Sales
✔ Stock Adjustment
✔ Stock In / Stock Out
✔ Inventory Dashboard
✔ Reports (Low Stock, Stock History, Daily Sales, etc.)
✔ User Authentication (Laravel Breeze)
✔ Search + Pagination
✔ Reusable Layout with Tailwind
✔ Soft Deletes
✔ Validation (Request Classes)
🚀 STEP 1 — Create Laravel 12 Project
bash
composer create-project laravel/laravel inventory-system
cd inventory-system
🚀 STEP 2 — Install AUTH (Laravel Breeze)
bash
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install
npm run dev
php artisan migrate
🚀 STEP 3 — Create Modules
We will create:⭐ Categories
⭐ Products
⭐ Suppliers
⭐ Purchases
⭐ Sales
⭐ Inventory Logs (Track every change in stock)
bash
php artisan make:model Category -mcr
php artisan make:model Product -mcr
php artisan make:model Supplier -mcr
php artisan make:model Purchase -mcr
php artisan make:model Sale -mcr
php artisan make:model StockLog -m
php artisan make:request ProductRequest
php artisan make:request CategoryRequest
php artisan make:request SupplierRequest
📌 STEP 4 — Migrations
📍 categories table
Run commands:
bash
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->text('description')->nullable();
$table->timestamps();
});
bash
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->string('sku')->unique();
$table->integer('stock')->default(0);
$table->decimal('price', 10, 2);
$table->decimal('cost', 10, 2)->nullable();
$table->text('description')->nullable();
$table->softDeletes();
$table->timestamps();
});
bash
Schema::create('suppliers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('phone')->nullable();
$table->string('email')->nullable();
$table->text('address')->nullable();
$table->timestamps();
});
bash
Schema::create('purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('supplier_id')->nullable()->constrained()->onDelete('set null');
$table->integer('quantity');
$table->decimal('cost_price', 10, 2);
$table->timestamps();
});
bash
Schema::create('sales', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->integer('quantity');
$table->decimal('sell_price', 10, 2);
$table->timestamps();
});
bash
Schema::create('stock_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->string('type'); // purchase, sale, manual-in, manual-out
$table->integer('quantity');
$table->string('note')->nullable();
$table->timestamps();
});
🚀 STEP 5 — MODELS (Relationships)
Product.php
bash
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'category_id', 'name', 'sku', 'stock', 'price', 'cost', 'description',
];
public function category() { return $this->belongsTo(Category::class); }
public function purchases() { return $this->hasMany(Purchase::class); }
public function sales() { return $this->hasMany(Sale::class); }
public function logs() { return $this->hasMany(StockLog::class); }
}
bash
class Category extends Model
{
use HasFactory;
protected $fillable = ['name', 'description'];
public function products() { return $this->hasMany(Product::class); }
}
bash
class Supplier extends Model
{
use HasFactory;
protected $fillable = ['name', 'email', 'phone', 'address'];
public function purchases() { return $this->hasMany(Purchase::class); }
}
🚀 STEP 6 — Controllers (Inventory Logic)
Example: PurchaseController@store
bash
public function store(Request $request)
{
$request->validate([
'product_id' => 'required',
'supplier_id' => 'nullable',
'quantity' => 'required|integer|min:1',
'cost_price' => 'required|numeric|min:0',
]);
$purchase = Purchase::create($request->all());
// Update stock
$product = Product::find($request->product_id);
$product->stock += $request->quantity;
$product->cost = $request->cost_price;
$product->save();
// Log
StockLog::create([
'product_id' => $product->id,
'type' => 'purchase',
'quantity' => $request->quantity,
'note' => 'Stock purchased',
]);
return back()->with('success', 'Purchase added + stock updated.');
}
bash
public function store(Request $request)
{
$request->validate([
'product_id' => 'required',
'quantity' => 'required|min:1',
'sell_price' => 'required',
]);
$product = Product::find($request->product_id);
if ($product->stock < $request->quantity) {
return back()->with('error', 'Not enough stock!');
}
Sale::create($request->all());
// update stock
$product->stock -= $request->quantity;
$product->save();
StockLog::create([
'product_id' => $product->id,
'type' => 'sale',
'quantity' => $request->quantity,
'note' => 'Sale processed',
]);
return back()->with('success', 'Sale created + stock reduced.');
}
🚀 STEP 7 — Routes
bash
Route::middleware(['auth'])->group(function () {
Route::resource('categories', CategoryController::class);
Route::resource('products', ProductController::class);
Route::resource('suppliers', SupplierController::class);
Route::resource('purchases', PurchaseController::class)->only(['index','create','store']);
Route::resource('sales', SaleController::class)->only(['index','create','store']);
Route::get('/stock/logs', [StockLogController::class, 'index'])->name('stock.logs');
});
🚀 STEP 8 — Dashboard Page
Create: resources/views/dashboard.blade.php
bash
@extends('layouts.app')
@section('content')
<div class="p-6">
<h1 class="text-2xl font-bold mb-6">Inventory Dashboard</h1>
<div class="grid grid-cols-4 gap-4">
<div class="bg-white p-4 shadow rounded">
<h2 class="text-xl">Total Products</h2>
<p class="text-4xl">{{ \App\Models\Product::count() }}</p>
</div>
<div class="bg-white p-4 shadow rounded">
<h2 class="text-xl">Low Stock</h2>
<p class="text-4xl">{{ \App\Models\Product::where('stock','<',5)->count() }}</p>
</div>
<div class="bg-white p-4 shadow rounded">
<h2 class="text-xl">Total Purchases</h2>
<p class="text-4xl">{{ \App\Models\Purchase::count() }}</p>
</div>
<div class="bg-white p-4 shadow rounded">
<h2 class="text-xl">Total Sales</h2>
<p class="text-4xl">{{ \App\Models\Sale::count() }}</p>
</div>
</div>
</div>
@endsection
🚀 STEP 9 — Inventory Reports
Create methods in StockLogController.
bash
public function index()
{
$logs = StockLog::with('product')->latest()->paginate(20);
return view('stock.logs', compact('logs'));
}
🚀 STEP 10 — Run Project
bash
php artisan serve
⭐ PART 1 — FULL PROJECT FOLDER STRUCTURE
inventory-system/
│
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── CategoryController.php
│ │ │ ├── ProductController.php
│ │ │ ├── SupplierController.php
│ │ │ ├── PurchaseController.php
│ │ │ ├── SaleController.php
│ │ │ ├── StockLogController.php
│ │ │ └── DashboardController.php
│ │ ├── Middleware/
│ │ └── Requests/
│ │ ├── CategoryRequest.php
│ │ ├── ProductRequest.php
│ │ └── SupplierRequest.php
│ ├── Models/
│ │ ├── Category.php
│ │ ├── Product.php
│ │ ├── Supplier.php
│ │ ├── Purchase.php
│ │ ├── Sale.php
│ │ └── StockLog.php
│
├── database/
│ ├── migrations/
│ ├── seeders/
│ └── factories/
│
├── resources/
│ ├── views/
│ │ ├── layouts/
│ │ │ └── app.blade.php
│ │ ├── dashboard.blade.php
│ │ ├── categories/
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ │ ├── edit.blade.php
│ │ └── products/
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ │ ├── edit.blade.php
│ │ └── suppliers/
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ │ ├── edit.blade.php
│ │ ├── purchases/
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ ├── sales/
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ └── stock/
│ │ └── logs.blade.php
│ │
│ ├── css/
│ └── js/
│
└── routes/
└── web.php
⭐ PART 2 — FULL CONTROLLERS (CRUD + LOGIC)
bash
namespace App\Http\Controllers;
use App\Models\Category;
use App\Http\Requests\CategoryRequest;
class CategoryController extends Controller
{
public function index()
{
$categories = Category::latest()->paginate(10);
return view('categories.index', compact('categories'));
}
public function create()
{
return view('categories.create');
}
public function store(CategoryRequest $request)
{
Category::create($request->validated());
return redirect()->route('categories.index')->with('success', 'Category created.');
}
public function edit(Category $category)
{
return view('categories.edit', compact('category'));
}
public function update(CategoryRequest $request, Category $category)
{
$category->update($request->validated());
return redirect()->route('categories.index')->with('success', 'Category updated.');
}
public function destroy(Category $category)
{
$category->delete();
return redirect()->route('categories.index')->with('success', 'Category deleted.');
}
}
bash
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Category;
use App\Http\Requests\ProductRequest;
class ProductController extends Controller
{
public function index()
{
$products = Product::with('category')->latest()->paginate(10);
return view('products.index', compact('products'));
}
public function create()
{
$categories = Category::all();
return view('products.create', compact('categories'));
}
public function store(ProductRequest $request)
{
Product::create($request->validated());
return redirect()->route('products.index')->with('success', 'Product added.');
}
public function edit(Product $product)
{
$categories = Category::all();
return view('products.edit', compact('product','categories'));
}
public function update(ProductRequest $request, Product $product)
{
$product->update($request->validated());
return redirect()->route('products.index')->with('success', 'Product updated.');
}
public function destroy(Product $product)
{
$product->delete();
return back()->with('success', 'Product deleted.');
}
}
bash
namespace App\Http\Controllers;
use App\Models\Supplier;
use App\Http\Requests\SupplierRequest;
class SupplierController extends Controller
{
public function index()
{
$suppliers = Supplier::latest()->paginate(10);
return view('suppliers.index', compact('suppliers'));
}
public function create()
{
return view('suppliers.create');
}
public function store(SupplierRequest $request)
{
Supplier::create($request->validated());
return back()->with('success','Supplier added.');
}
public function edit(Supplier $supplier)
{
return view('suppliers.edit', compact('supplier'));
}
public function update(SupplierRequest $request, Supplier $supplier)
{
$supplier->update($request->validated());
return back()->with('success','Supplier updated.');
}
public function destroy(Supplier $supplier)
{
$supplier->delete();
return back()->with('success','Supplier deleted.');
}
}
bash
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Supplier;
use App\Models\Purchase;
use App\Models\StockLog;
use Illuminate\Http\Request;
class PurchaseController extends Controller
{
public function index()
{
$purchases = Purchase::with(['product','supplier'])->latest()->paginate(10);
return view('purchases.index', compact('purchases'));
}
public function create()
{
return view('purchases.create', [
'products' => Product::all(),
'suppliers' => Supplier::all(),
]);
}
public function store(Request $request)
{
$request->validate([
'product_id' => 'required',
'supplier_id' => 'nullable',
'quantity' => 'required|integer',
'cost_price' => 'required',
]);
Purchase::create($request->all());
$product = Product::find($request->product_id);
$product->stock += $request->quantity;
$product->cost = $request->cost_price;
$product->save();
StockLog::create([
'product_id'=>$product->id,
'type'=>'purchase',
'quantity'=>$request->quantity,
]);
return back()->with('success','Purchase added + Stock updated.');
}
}
bash
namespace App\Http\Controllers;
use App\Models\Product;
use App\Models\Sale;
use App\Models\StockLog;
use Illuminate\Http\Request;
class SaleController extends Controller
{
public function index()
{
$sales = Sale::with('product')->latest()->paginate(10);
return view('sales.index', compact('sales'));
}
public function create()
{
return view('sales.create', [
'products' => Product::all(),
]);
}
public function store(Request $request)
{
$request->validate([
'product_id'=>'required',
'quantity'=>'required',
'sell_price'=>'required'
]);
$product = Product::find($request->product_id);
if ($product->stock < $request->quantity) {
return back()->with('error','Not enough stock!');
}
Sale::create($request->all());
$product->stock -= $request->quantity;
$product->save();
StockLog::create([
'product_id'=>$product->id,
'type'=>'sale',
'quantity'=>$request->quantity
]);
return back()->with('success','Sale completed + stock updated.');
}
}
bash
namespace App\Http\Controllers;
use App\Models\StockLog;
class StockLogController extends Controller
{
public function index()
{
$logs = StockLog::with('product')->latest()->paginate(20);
return view('stock.logs', compact('logs'));
}
}
PART 3 — FULL BLADE VIEWS
I will now generate all Blade files:
Layout
Categories (index, create, edit)
Products (index, create, edit)
Suppliers
Purchases
Sales
Stock Logs
Dashboard
Because this is a LOT of code, I will deliver them cleanly section by section.
I tried to add the full admin Blade layout + all view files into the canvas (so you could preview/download them), but the canvas update failed due to an internal replacement error when writing the document. I attempted to replace the canvas contents with the generated Blade files, but that write failed — sorry about that.