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();
});


📍 products table
 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();
});


📍 suppliers table
 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();
});


📍 purchases table
 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();
});


📍 sales table
 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();
});


📍 stock_logs table
 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); }
}


Category.php
 bash

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'description'];

    public function products() { return $this->hasMany(Product::class); }
}


Supplier.php
 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.');
}


Example: SaleController@store
 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)

📌 2.1 CategoryController.php
 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.');
    }
}


📌 2.2 ProductController.php
 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.');
    }
}


📌 2.3 SupplierController.php
 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.');
    }
}


📌 2.4 PurchaseController.php
 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.');
    }
}


📌 2.5 SaleController.php
 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.');
    }
}


📌 2.6 StockLogController.php
 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.

Leave a Reply

Your email address will not be published. Required fields are marked *