Skip to main content

Overview

The Product model represents products and inventory items in the ERP system. It manages product information, pricing, categories, units of measurement, and integrates with the inventory management system. Namespace: App\Models\Products\Product Database Table: products Traits: SoftDeletes

Properties

Fillable Attributes

category_id
integer
required
Foreign key to the Category model
unit_id
integer
required
Foreign key to the Unit model (measurement unit)
name
string
required
Product name
slug
string
required
URL-friendly version of the product name
sku
string
required
Stock Keeping Unit - unique product identifier
description
text
Detailed product description
image_path
string
Path to the product image file
price
decimal
required
Selling price of the product
cost
decimal
required
Cost price of the product
is_active
boolean
default:"true"
Whether the product is active and available for sale
is_stockable
boolean
default:"true"
Whether the product requires inventory tracking

Relationships

category()

Returns the category this product belongs to.
category
BelongsTo
Returns a Category model instance
$product->category; // Category object
$product->category->name; // Category name

unit()

Returns the unit of measurement for this product.
unit
BelongsTo
Returns a Unit model instance
$product->unit; // Unit object
$product->unit->name; // 'Unidad', 'Caja', 'Litro', etc.
$product->unit->abbreviation; // 'un', 'cj', 'lt', etc.

stocks()

Returns stock information across all warehouses.
stocks
HasMany
Returns a collection of InventoryStock models
$product->stocks; // Collection of InventoryStock objects

Accessors

formattedPrice

Returns the product price formatted with currency symbol.
$product->formattedPrice; // '$ 150.00'
Implementation:
public function getFormattedPriceAttribute(): string
{
    $config = general_config();
    $symbol = $config->currency_symbol ?? '$';
    
    return $symbol . ' ' . number_format($this->price, 2);
}

totalStock

Returns the total stock quantity across all warehouses.
$product->totalStock; // Sum of all warehouse stocks
Implementation:
public function getTotalStockAttribute()
{
    return $this->stocks()->sum('quantity');
}

Scopes

withIndexRelations()

Eager loads relationships needed for index pages and list views.
Product::withIndexRelations()->get();
This scope loads:
  • category (id, name)
  • unit (id, name, abbreviation)

activo()

Filters only active products.
Product::activo()->get();
Implementation:
public function scopeActivo(Builder $query): Builder
{
    return $query->where('is_active', true);
}

inactivo()

Filters only inactive products.
Product::inactivo()->get();
Implementation:
public function scopeInactivo(Builder $query): Builder
{
    return $query->where('is_active', false);
}

stockable()

Filters only products that require inventory tracking.
Product::stockable()->get();
Implementation:
public function scopeStockable(Builder $query): Builder
{
    return $query->where('is_stockable', true);
}

Usage Examples

Creating a Product

use App\Models\Products\Product;

$product = Product::create([
    'category_id' => 1,
    'unit_id' => 1,
    'name' => 'Producto Premium',
    'slug' => 'producto-premium',
    'sku' => 'PRD-001',
    'description' => 'Descripción detallada del producto',
    'price' => 150.00,
    'cost' => 90.00,
    'is_active' => true,
    'is_stockable' => true
]);

Querying Products

// Get all active products with relationships
$products = Product::activo()
    ->withIndexRelations()
    ->orderBy('name')
    ->get();

// Access product data
foreach ($products as $product) {
    echo $product->name;
    echo $product->sku;
    echo $product->formattedPrice;
    echo $product->category->name;
    echo $product->unit->abbreviation;
}

Working with Product Stock

// Get total stock across all warehouses
$totalStock = $product->totalStock;

// Get stock by warehouse
$stocks = $product->stocks()->with('warehouse')->get();
foreach ($stocks as $stock) {
    echo $stock->warehouse->name . ': ' . $stock->quantity;
}

// Check if product is in stock
if ($product->totalStock > 0) {
    // Product available
} else {
    // Out of stock
}

Filtering Products by Category

// Get products in specific category
$categoryProducts = Product::where('category_id', $categoryId)
    ->activo()
    ->get();

// Get products with category name
$products = Product::withIndexRelations()
    ->whereHas('category', function ($query) {
        $query->where('name', 'Electronics');
    })
    ->get();

Price and Margin Calculations

// Calculate profit margin
$margin = $product->price - $product->cost;
$marginPercentage = (($product->price - $product->cost) / $product->cost) * 100;

echo "Margin: {$margin}";
echo "Margin %: {$marginPercentage}%";

// Get products by price range
$expensiveProducts = Product::where('price', '>=', 1000)
    ->activo()
    ->get();

Managing Product Status

// Activate product
$product->is_active = true;
$product->save();

// Deactivate product
$product->is_active = false;
$product->save();

// Get inactive products
$inactiveProducts = Product::inactivo()->get();

Stockable vs Non-Stockable Products

// Get only stockable products
$stockableProducts = Product::stockable()->get();

// Get service-type products (non-stockable)
$services = Product::where('is_stockable', false)->get();

// Create a service product
$service = Product::create([
    'name' => 'Installation Service',
    'sku' => 'SRV-001',
    'price' => 500.00,
    'cost' => 0.00,
    'is_stockable' => false, // No inventory tracking needed
    'category_id' => $serviceCategory->id,
    'unit_id' => $serviceUnit->id
]);
// Search products by name or SKU
$searchTerm = 'premium';
$results = Product::activo()
    ->where(function ($query) use ($searchTerm) {
        $query->where('name', 'LIKE', "%{$searchTerm}%")
              ->orWhere('sku', 'LIKE', "%{$searchTerm}%");
    })
    ->withIndexRelations()
    ->get();

Low Stock Products

// Get products with low stock
$lowStockProducts = Product::stockable()
    ->whereHas('stocks', function ($query) {
        $query->whereRaw('quantity <= min_stock');
    })
    ->with('stocks')
    ->get();

foreach ($lowStockProducts as $product) {
    foreach ($product->stocks as $stock) {
        if ($stock->quantity <= $stock->min_stock) {
            echo "{$product->name} low in {$stock->warehouse->name}";
        }
    }
}

Product Sales Analysis

// Get best-selling products
$bestSellers = Product::withCount(['saleItems' => function ($query) {
        $query->whereHas('sale', function ($q) {
            $q->where('status', 'completed')
              ->whereMonth('sale_date', now()->month);
        });
    }])
    ->orderBy('sale_items_count', 'desc')
    ->take(10)
    ->get();

Bulk Operations

// Update prices by percentage
Product::where('category_id', $categoryId)
    ->each(function ($product) {
        $product->price = $product->price * 1.10; // 10% increase
        $product->save();
    });

// Deactivate out-of-stock products
Product::stockable()
    ->whereDoesntHave('stocks', function ($query) {
        $query->where('quantity', '>', 0);
    })
    ->update(['is_active' => false]);

Product with Image

// Store product with image
$product = Product::create([
    'name' => 'Product with Image',
    'sku' => 'PRD-IMG-001',
    'image_path' => $request->file('image')->store('products', 'public'),
    // ... other fields
]);

// Get image URL
$imageUrl = Storage::url($product->image_path);

Build docs developers (and LLMs) love