Overview
ShelfWise uses a flexible product model that supports both simple products and complex multi-variant products. Each product can have multiple variants (e.g., sizes, colors), and each variant can be sold in different packaging types (e.g., loose items, boxes, cartons).Product Structure
The product hierarchy in ShelfWise follows this structure:Product Model
Products are defined inapp/Models/Product.php and include:
Product name displayed to customers and staff
URL-friendly identifier, auto-generated from name
Reference to ProductType (e.g., retail, pharmaceutical, food)
Optional category for organization
Whether this product has multiple variants
Controls visibility in POS and storefront. See state management below.
Whether inventory tracking is enabled for this product
Whether tax should be applied to this product
Whether to highlight this product in storefront
Product State Management
Products use bothis_active flag and soft deletes for different purposes:
| State | is_active | deleted_at | Behavior |
|---|---|---|---|
| Active | true | null | Fully visible: POS, Storefront, Admin, Reports |
| Inactive | false | null | Hidden from POS/Storefront, still visible in Admin/Reports |
| Deleted | N/A | timestamp | Completely hidden, can be restored |
When to Use is_active = false
- Temporarily disable sales (out of season, pending restock)
- Hide from customer-facing channels without losing data
- Maintain in reports and historical data
- Quick toggle for product availability
When to Use Soft Delete
- Permanently remove from all active operations
- Discontinue product while preserving historical order data
- Complete removal from POS, Storefront, and Admin views
Product Variants
Variants represent specific variations of a product. Defined inapp/Models/ProductVariant.php:
Stock Keeping Unit - unique identifier for inventory tracking
Barcode for scanning at POS
Variant-specific name (e.g., “Size M”, “Blue”)
JSON attributes defining the variant (e.g.,
{"size": "M", "color": "Blue"})Selling price per base unit
Cost per base unit. Automatically updated using weighted average method.
Minimum stock level before triggering reorder alert
Name of the smallest sellable unit (e.g., “Piece”, “Tablet”, “ml”)
Whether this variant is available for sale
Whether this variant is visible in the online storefront
Variant State Management
Variants also use dual state management:- Inactive Variant
- Deleted Variant
Set
is_active = false when:- Temporarily suspend sales of this specific variant
- Variant out of stock but may return (e.g., seasonal size)
- Testing new variant before public release
- Inventory still tracked and visible in admin
Stock Calculations
Variants provide computed attributes for stock levels:Use eager loading with
inventoryLocations to avoid N+1 queries:Weighted Average Cost
ShelfWise automatically calculates cost price using the weighted average method when stock is purchased:Example Calculation
- You have 100 units at 1,000)
- You purchase 50 more units at 600)
- New weighted average: (600) / (100 + 50) = $10.67 per unit
Creating Products
Products are created through theProductService in app/Services/ProductService.php:
Simple Products (No Variants)
For products without variants, omit thehas_variants and variants fields:
Updating Products
Update products through the service layer:Updating Variants
Variants can be updated individually:When you update a variant’s price or cost_price, all associated packaging types are automatically updated proportionally based on their
units_per_package ratio.Multi-Tenant Isolation
Always use the service layer or ensure tenant filtering:app/Http/Controllers/ProductController.php:35 for implementation examples.
Query Scopes
Products and variants provide helpful query scopes:Product Types
Products are categorized by type, which determines available features:Standard retail products with simple pricing
Requires batch tracking and expiry dates
Requires expiry date tracking
May require serial number tracking
Best Practices
- Performance
- Data Integrity
- Security
- Always eager load relationships to avoid N+1 queries
- Use
with(['inventoryLocations', 'product', 'packagingTypes'])when fetching variants - Cache product lists with
Cache::tags(["tenant:{$tenantId}:products:list"])
Related Models
- InventoryLocation - Tracks stock quantities per location
- ProductPackagingType - Defines different packaging units
- StockMovement - Audit trail of all inventory changes
- ProductCategory - Optional product categorization
- ProductType - Defines product behavior and requirements