Skip to main content
ShelfWise models follow Laravel Eloquent ORM conventions with multi-tenant isolation, UUID primary keys, and comprehensive relationship definitions.

Model Conventions

  • UUID Primary Keys: All models use UUID via HasUuid trait
  • Tenant Isolation: BelongsToTenant trait ensures data segregation
  • Soft Deletes: Most models support soft deletion for data recovery
  • Explicit Query Builder: Always use Model::query()-> prefix

Product

Represents a product in the inventory system. Products can have multiple variants (sizes, colors) or be standalone items. Location: app/Models/Product.php

Properties

id
int
Primary key
tenant_id
int
required
Tenant owner
shop_id
int
required
Shop location
product_type_id
int
required
Product type (General, Pharmacy, Retail, etc.)
category_id
int
Product category
name
string
required
Product name
slug
string
required
URL-friendly identifier (unique per tenant)
description
string
Product description
custom_attributes
array
Type-specific attributes (e.g., requires_prescription for pharmacy)
has_variants
bool
default:"false"
Whether product has multiple variants
is_active
bool
default:"true"
Product visibility status
track_stock
bool
default:"true"
Enable inventory tracking
is_taxable
bool
default:"true"
Subject to VAT
Featured on storefront

Relationships

tenant
BelongsTo
Tenant that owns this product
shop
BelongsTo
Shop where product is sold
type
BelongsTo
Product type definition
category
BelongsTo
Product category
variants
HasMany
Product variants (sizes, colors, etc.)
images
MorphMany
Product images

State Management

Products use BOTH is_active flag AND soft deletes for different purposes:
Stateis_activedeleted_atBehavior
ActivetruenullFully visible: POS, Storefront, Admin, Reports
InactivefalsenullHidden from POS/Storefront, visible in Admin/Reports
DeletedN/AtimestampCompletely hidden, can be restored via soft delete recovery
Use is_active = false for:
  • Temporarily disable sales (out of season, pending restock)
  • Hide from customer-facing channels without losing data
Use soft delete for:
  • Permanently remove from all active operations
  • Discontinue product while preserving historical order data

Scopes

// Get only active products
Product::query()->active()->get();

// Get products for specific tenant and shop
Product::query()
    ->where('tenant_id', $tenant->id)
    ->where('shop_id', $shop->id)
    ->get();

Example

use App\Models\Product;

$product = Product::query()->create([
    'tenant_id' => $tenant->id,
    'shop_id' => $shop->id,
    'product_type_id' => 1,
    'category_id' => 5,
    'name' => 'Pain Relief Tablets',
    'slug' => 'pain-relief-tablets',
    'description' => '500mg paracetamol tablets',
    'has_variants' => false,
    'is_active' => true,
    'track_stock' => true,
    'is_taxable' => true,
]);

// Load relationships
$product = Product::query()
    ->with(['type', 'category', 'variants.packagingTypes'])
    ->find($productId);

Order

Represents a customer order with status tracking, payments, and fulfillment workflow. Location: app/Models/Order.php

Properties

id
int
Primary key
tenant_id
int
required
Tenant owner
shop_id
int
required
Shop location
customer_id
int
Customer (optional for guest orders)
order_number
string
Auto-generated unique order number (ORD-YYYYMMDD-####)
order_type
OrderType
required
POS, CUSTOMER, or INTERNAL
status
OrderStatus
required
Current order status
payment_status
PaymentStatus
required
Payment status (UNPAID, PARTIAL, PAID, REFUNDED)
subtotal
decimal
Sum of line items before tax and shipping
tax_amount
decimal
Total tax amount
discount_amount
decimal
Total discount applied
shipping_cost
decimal
Shipping cost
total_amount
decimal
Final total including tax and shipping
paid_amount
decimal
Amount paid so far

Relationships

tenant
BelongsTo
Tenant that owns this order
shop
BelongsTo
Shop where order was placed
customer
BelongsTo
Customer who placed the order
items
HasMany
Order line items
payments
HasMany
Payment records
returns
HasMany
Return records
receipts
HasMany
Generated receipts

Methods

canEdit()
bool
Check if order can be edited (PENDING or CONFIRMED status)
canCancel()
bool
Check if order can be cancelled (not DELIVERED, CANCELLED, or REFUNDED)
calculateTotals()
void
Recalculate order totals from items
remainingBalance()
float
Get unpaid balance
isFullyPaid()
bool
Check if order is completely paid

Scopes

// Filter by status
Order::query()->pending()->get();
Order::query()->active()->get(); // All active statuses

// Filter by type
Order::query()->posSales()->get();
Order::query()->customerOrders()->get();

// Filter by shop and tenant
Order::query()
    ->forTenant($tenantId)
    ->forShop($shopId)
    ->get();

Example

use App\Models\Order;
use App\Enums\OrderStatus;
use App\Enums\PaymentStatus;

$order = Order::create([
    'tenant_id' => $tenant->id,
    'shop_id' => $shop->id,
    'customer_id' => $customer->id,
    'order_type' => OrderType::CUSTOMER,
    'status' => OrderStatus::PENDING,
    'payment_status' => PaymentStatus::UNPAID,
    'shipping_cost' => 500.00,
    'created_by' => auth()->id(),
]);

// Load with relationships
$order = Order::query()
    ->with([
        'items.productVariant.product',
        'customer',
        'payments',
    ])
    ->find($orderId);

// Calculate totals
$order->calculateTotals();
$order->save();

// Check status
if ($order->canEdit()) {
    // Allow editing
}

Customer

Represents an e-commerce customer (separate from User model which represents staff). Location: app/Models/Customer.php

Properties

id
int
Primary key
tenant_id
int
required
Tenant owner
preferred_shop_id
int
Default shop for customer
first_name
string
required
Customer first name
last_name
string
required
Customer last name
email
string
required
Customer email (unique per tenant)
phone
string
Customer phone number
is_active
bool
default:"true"
Account active status
marketing_opt_in
bool
default:"false"
Marketing communications consent
account_balance
decimal
Current balance (for credit accounts)
credit_limit
decimal
Maximum credit allowed
total_purchases
decimal
Lifetime purchase total

Relationships

tenant
BelongsTo
Tenant that owns this customer
preferredShop
BelongsTo
Customer’s preferred shop
addresses
HasMany
Customer addresses
orders
HasMany
Customer orders
carts
HasMany
Shopping carts
creditTransactions
HasMany
Credit transaction history

Methods

availableCredit()
float|null
Get remaining credit available
canPurchaseOnCredit(amount)
bool
Check if customer can make a purchase on credit
unpaidOrders()
HasMany
Get all orders with unpaid or partial payment status

Scopes

// Active customers only
Customer::query()->active()->get();

// Filter by tenant
Customer::query()->forTenant($tenantId)->get();

// Filter by shop
Customer::query()->forShop($shopId)->get();

Example

use App\Models\Customer;

$customer = Customer::create([
    'tenant_id' => $tenant->id,
    'preferred_shop_id' => $shop->id,
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => '[email protected]',
    'phone' => '+2348012345678',
    'password' => Hash::make('password'),
    'is_active' => true,
    'credit_limit' => 50000.00,
]);

// Check credit availability
if ($customer->canPurchaseOnCredit(25000)) {
    // Proceed with purchase
}

// Get available credit
$available = $customer->availableCredit(); // 50000 - account_balance

Shop

Represents a physical or virtual store location within a tenant. Location: app/Models/Shop.php

Properties

id
int
Primary key
tenant_id
int
required
Tenant owner
shop_type_id
int
required
Shop type (Retail, Restaurant, Pharmacy, etc.)
name
string
required
Shop name
slug
string
required
URL-friendly identifier
config
array
Type-specific configuration
inventory_model
InventoryModel
Inventory tracking model (PERPETUAL, PERIODIC)
is_active
bool
default:"true"
Shop active status
storefront_enabled
bool
default:"false"
Enable e-commerce storefront
storefront_settings
array
Storefront configuration (shipping, theme, etc.)
currency
string
default:"NGN"
Shop currency code
currency_symbol
string
default:"₦"
Currency symbol
vat_enabled
bool
default:"false"
Enable VAT calculations
vat_rate
decimal
default:"7.5"
VAT rate percentage

Relationships

tenant
BelongsTo
Tenant that owns this shop
type
BelongsTo
Shop type definition
users
BelongsToMany
Staff assigned to this shop
products
HasMany
Products sold at this shop
services
HasMany
Services offered at this shop
carts
HasMany
Shopping carts for this shop
taxSettings
HasOne
Shop-specific tax settings

Example

use App\Models\Shop;

$shop = Shop::query()->create([
    'tenant_id' => $tenant->id,
    'shop_type_id' => 1,
    'name' => 'Main Street Pharmacy',
    'slug' => 'main-street-pharmacy',
    'address' => '123 Main Street',
    'city' => 'Lagos',
    'state' => 'Lagos',
    'country' => 'Nigeria',
    'currency' => 'NGN',
    'currency_symbol' => '₦',
    'vat_enabled' => true,
    'vat_rate' => 7.5,
    'storefront_enabled' => true,
]);

// Get shop with users
$shop = Shop::query()
    ->with(['users', 'products', 'type'])
    ->find($shopId);

Additional Models

Location: app/Models/ProductVariant.phpRepresents a specific variant of a product (size, color, etc.)

Key Properties

  • product_id - Parent product
  • sku - Stock Keeping Unit (unique)
  • barcode - Barcode number
  • name - Variant name (e.g., “Large”, “Red”)
  • attributes - Variant attributes JSON
  • price - Selling price
  • cost_price - Purchase cost
  • base_unit_name - Base unit (e.g., “Pack”, “Bottle”)
  • reorder_level - Minimum stock level

Relationships

  • product - Parent product
  • packagingTypes - Available packaging options
  • inventoryLocations - Stock levels per location

Query Best Practices

// Good - Explicit query builder
Product::query()->where('tenant_id', $tenantId)->get();
Order::query()->find($id);

// Bad - Implicit query builder
Product::where('tenant_id', $tenantId)->get();
Order::find($id);
// Always filter by tenant_id
Product::query()
    ->where('tenant_id', auth()->user()->tenant_id)
    ->where('shop_id', $shop->id)
    ->get();
// Prevent N+1 queries
Order::query()
    ->with([
        'items.productVariant.product',
        'customer',
        'payments',
    ])
    ->get();
// Include soft deleted records
Product::query()->withTrashed()->get();

// Only soft deleted records
Product::query()->onlyTrashed()->get();

// Restore soft deleted record
$product->restore();

Build docs developers (and LLMs) love