Product Management Guide
Overview
The Product Management system provides comprehensive catalog management for all product types, from simple physical goods to complex master/variant configurations. Built on Domain-Driven Design principles with CQRS patterns, it offers flexible product modeling, lifecycle management, and rich metadata support.
Product Types
The system supports seven distinct product types, each with specific characteristics:
Physical Products
Traditional tangible goods requiring inventory tracking and shipping.
ProductType::PHYSICAL
// Characteristics:
- requiresShipping(): true
- hasInventory(): true
- isTangible(): true
- canHaveVariants(): true
- taxCategory(): 'physical_goods'Examples: Laptops, clothing, books, furniture Use Cases: E-commerce stores, retail inventory Features: Full inventory tracking, shipping calculations, variant support
Digital Products
Downloadable or streamable digital content.
ProductType::DIGITAL
// Characteristics:
- requiresShipping(): false
- hasInventory(): false
- isDownloadable(): true
- canHaveVariants(): true
- taxCategory(): 'digital_goods'Examples: Software, e-books, music files, PDF documents Use Cases: Digital marketplaces, content platforms Features: Download links, license management, no inventory tracking
Services
Professional services or consultations.
ProductType::SERVICE
// Characteristics:
- requiresShipping(): false
- hasInventory(): false
- requiresFulfillment(): true
- canHaveVariants(): true
- taxCategory(): 'services'Examples: Consulting hours, maintenance contracts, training sessions Use Cases: Service businesses, professional services Features: Booking management, resource allocation, service-specific pricing
Subscriptions
Recurring products billed periodically.
ProductType::SUBSCRIPTION
// Characteristics:
- requiresShipping(): false
- hasInventory(): false
- isRecurring(): true
- canHaveVariants(): true
- taxCategory(): 'subscription_services'Examples: Monthly memberships, SaaS subscriptions, subscription boxes Use Cases: Membership platforms, subscription businesses Features: Recurring billing, renewal management, tier variations
Bundles
Collections of multiple products sold together.
ProductType::BUNDLE
// Characteristics:
- requiresShipping(): false (depends on contents)
- hasInventory(): false
- taxCategory(): 'mixed'Examples: Starter packs, gift sets, combo deals Use Cases: Product bundles, promotional packages Features: Component tracking, bundle pricing, inventory aggregation
Gift Cards
Prepaid store credit or value cards.
ProductType::GIFT_CARD
// Characteristics:
- requiresShipping(): false
- hasInventory(): true (for physical cards)
- taxCategory(): 'gift_cards'Examples: Store gift cards, prepaid vouchers Use Cases: Gift card programs, promotional credits Features: Balance tracking, redemption codes, expiration dates
Virtual Products
Non-physical, non-downloadable products.
ProductType::VIRTUAL
// Characteristics:
- requiresShipping(): false
- hasInventory(): false
- isDownloadable(): true
- taxCategory(): 'virtual_goods'Examples: Game credits, virtual currencies, in-app purchases Use Cases: Gaming platforms, virtual economies Features: Virtual delivery, account crediting
Product Structure
Core Properties
Every product has these fundamental properties:
[
'id' => '01hwxyz123abc456def789ghi0', // Auto-generated ULID
'name' => 'Product Name', // Display name (3-255 chars)
'slug' => 'product-name', // URL-friendly identifier
'sku' => 'PROD-001', // Stock Keeping Unit (unique)
'type' => 'physical', // Product type enum
'status' => 'active', // Status enum
'is_variant' => false, // Simple vs variant product
'parent_product_id' => null, // Master product ID (for variants)
// Pricing
'selling_price' => Money, // Retail price (Money VO)
'cost_price' => Money, // Cost basis (Money VO)
'currency' => 'USD', // Currency code
// Inventory
'stock_quantity' => 100, // Current stock level
'reserved_quantity' => 10, // Reserved for orders
'available_quantity' => 90, // Available = stock - reserved
'reorder_point' => 20, // Low stock threshold
'reorder_quantity' => 50, // Auto-reorder amount
'track_inventory' => true, // Enable inventory tracking
// Description
'short_description' => '...', // Brief summary
'description' => '...', // Full description
'meta_description' => '...', // SEO description
'meta_keywords' => '...', // SEO keywords
// Dimensions (for physical products)
'weight' => 2.5, // Weight in kg
'length' => 30.0, // Length in cm
'width' => 20.0, // Width in cm
'height' => 10.0, // Height in cm
'dimensions_unit' => 'cm', // Unit of measure
// Relationships
'category_id' => '01hwcat123abc456def789ghi0', // Product category
'supplier_id' => '01hwsup123abc456def789ghi0', // Primary supplier
'brand_id' => '01hwbrd123abc456def789ghi0', // Product brand
'tax_class_id' => '01hwtax123abc456def789ghi0', // Tax classification
// Settings
'is_featured' => false, // Featured product flag
'is_taxable' => true, // Subject to tax
'requires_shipping' => true, // Needs shipping
'allow_backorders' => false, // Accept orders when out of stock
// Metadata
'created_at' => '2025-01-15 10:00:00',
'updated_at' => '2025-01-15 14:30:00',
'deleted_at' => null, // Soft delete timestamp
]Money Value Object
All monetary amounts use the Money Value Object to prevent precision errors:
use App\Shared\Domain\ValueObjects\Money;
// Creating Money instances
$sellingPrice = Money::fromFloat(19.99, 'USD');
$costPrice = Money::fromCents(1200, 'USD'); // $12.00
// Accessing values
$sellingPrice->toFloat(); // 19.99
$sellingPrice->amount(); // 1999 (cents)
$sellingPrice->currency(); // "USD"
$sellingPrice->format(); // "USD 19.99"
// Arithmetic (returns new Money instance)
$profit = $sellingPrice->subtract($costPrice);
$total = $sellingPrice->multiply(10);For detailed pricing strategies, see the Pricing Strategy Guide.
Simple vs Master Products
Simple Products
Single SKU products with straightforward inventory tracking.
[
'name' => 'Laptop - Dell XPS 13',
'sku' => 'DELL-XPS13-001',
'type' => 'physical',
'is_variant' => false,
'parent_product_id' => null,
'selling_price' => Money::fromFloat(999.99),
'stock_quantity' => 50,
]Characteristics:
- One product = one SKU
- Single inventory record
- Direct pricing
- No variations
Best For:
- Unique items (specific laptop model)
- Products without variations
- Services with fixed pricing
- Digital downloads (single version)
Master Products
Configuration template for generating product variants.
[
'name' => 'T-Shirt - Cotton Basic',
'type' => 'physical',
'is_variant' => false,
'variant_configuration' => [
'attributes' => [
'Size' => ['S', 'M', 'L', 'XL'],
'Color' => ['Red', 'Blue', 'Black', 'White'],
],
],
]Characteristics:
- No direct SKU (variants have SKUs)
- Template for variant generation
- Defines variant attributes
- No direct inventory tracking
Generates: 16 variants (4 sizes x 4 colors)
For detailed variant management, see the Product Variants Guide.
Creating Products
Using CQRS Commands
The recommended approach using Command pattern:
use App\Modules\Operations\Application\Commands\CreateProductCommand;
use App\Modules\Operations\Application\DTOs\Product\CreateProductDTO;
use App\Shared\Application\Bus\CommandBus;
// In controller
public function store(CreateProductRequest $request): JsonResponse
{
$command = new CreateProductCommand(
dto: CreateProductDTO::fromArray($request->validated())
);
$product = $this->commandBus->dispatch($command);
return response()->json([
'message' => 'Product created successfully',
'data' => new ProductResource($product),
], 201);
}CreateProductDTO
Complete DTO with all available fields:
$dto = CreateProductDTO::fromArray([
// Required
'name' => 'Laptop - Dell XPS 13',
// Optional - Basic Info
'slug' => 'laptop-dell-xps-13',
'sku' => 'DELL-XPS13-001', // Auto-generated if not provided
'type' => 'physical', // Default: physical
'short_description' => 'Ultra-portable laptop',
'description' => 'Full product description...',
// Pricing
'selling_price' => 999.99, // Converted to Money VO
'cost_price' => 750.00,
'currency' => 'USD', // Default: USD
// Inventory
'stock_quantity' => 50, // Default: 0
'reorder_point' => 10,
'reorder_quantity' => 25,
'track_inventory' => true, // Default: true
'allow_backorders' => false,
// Dimensions
'weight' => 1.2, // kg
'length' => 30.0, // cm
'width' => 20.0,
'height' => 2.0,
'dimensions_unit' => 'cm',
// Relationships
'category_id' => '01hwcat123abc456def789ghi0',
'supplier_id' => '01hwsup123abc456def789ghi0',
'brand_id' => '01hwbrd123abc456def789ghi0',
'tax_class_id' => '01hwtax123abc456def789ghi0',
// Settings
'is_featured' => false,
'is_taxable' => true,
'requires_shipping' => true,
// SEO
'meta_description' => 'Buy Dell XPS 13...',
'meta_keywords' => 'laptop, dell, xps',
// Tags (array of tag IDs)
'tags' => ['01hwtag100abc456def789ghi0', '01hwtag200abc456def789ghi0', '01hwtag300abc456def789ghi0'],
]);API Request Example
Creating a product via API:
curl -X POST https://api.crm.test/api/v1/operations/products \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Laptop - Dell XPS 13",
"sku": "DELL-XPS13-001",
"type": "physical",
"selling_price": 999.99,
"cost_price": 750.00,
"stock_quantity": 50,
"reorder_point": 10,
"category_id": "01hwcat123abc456def789ghi0",
"supplier_id": "01hwsup123abc456def789ghi0",
"short_description": "Ultra-portable laptop with stunning display",
"description": "The Dell XPS 13 features...",
"weight": 1.2,
"length": 30,
"width": 20,
"height": 2,
"track_inventory": true
}'Response:
{
"message": "Product created successfully",
"data": {
"id": "01hwprd123abc456def789ghi0",
"name": "Laptop - Dell XPS 13",
"slug": "laptop-dell-xps-13",
"sku": "DELL-XPS13-001",
"type": "physical",
"status": "draft",
"selling_price": 999.99,
"cost_price": 750.00,
"stock_quantity": 50,
"available_quantity": 50,
"category": {
"id": "01hwcat123abc456def789ghi0",
"name": "Electronics"
},
"supplier": {
"id": "01hwsup123abc456def789ghi0",
"name": "Tech Supplies Inc"
},
"created_at": "2025-01-15T10:30:00Z"
}
}Updating Products
Using CQRS Commands
use App\Modules\Operations\Application\Commands\UpdateProductCommand;
use App\Modules\Operations\Application\DTOs\Product\UpdateProductDTO;
$command = new UpdateProductCommand(
productId: '01hwprd123abc456def789ghi0',
dto: UpdateProductDTO::fromArray($request->validated())
);
$product = $this->commandBus->dispatch($command);Partial Updates
UpdateProductDTO supports partial updates - only include fields you want to change:
$dto = UpdateProductDTO::fromArray([
'selling_price' => 899.99, // New price
'stock_quantity' => 75, // Update stock
// Other fields remain unchanged
]);API Request Example
curl -X PUT https://api.crm.test/api/v1/operations/products/01hwprd123abc456def789ghi0 \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"selling_price": 899.99,
"stock_quantity": 75,
"short_description": "Updated description"
}'Product Lifecycle Operations
For full lifecycle state machine details, see the Product Lifecycle Guide.
Activate Product
curl -X POST https://api.crm.test/api/v1/operations/products/01hwprd123abc456def789ghi0/activate \
-H "Authorization: Bearer {token}"Effect: Status changes to active, product visible in catalog, inventory tracking begins, fires ProductActivated domain event.
Deactivate Product
curl -X POST https://api.crm.test/api/v1/operations/products/01hwprd123abc456def789ghi0/deactivate \
-H "Authorization: Bearer {token}" \
-d '{"reason": "Seasonal product - off season"}'Effect: Status changes to inactive, product hidden from catalog, inventory preserved, can be reactivated later.
Discontinue Product
curl -X POST https://api.crm.test/api/v1/operations/products/01hwprd123abc456def789ghi0/discontinue \
-H "Authorization: Bearer {token}" \
-d '{"reason": "End of life - no longer manufactured"}'Effect: Status changes to discontinued, permanently removed from catalog, cannot be reactivated, historical data preserved.
Product Relationships
Categories
Hierarchical product categorization:
// Assign category during creation
'category_id' => '01hwcat123abc456def789ghi0'
// Category relationship
$product->category; // Returns Category model
$product->category->name; // "Electronics"
$product->category->parent; // Parent category (if nested)Examples: Electronics > Laptops > Gaming Laptops
Suppliers
Primary vendor for product sourcing:
'supplier_id' => '01hwsup123abc456def789ghi0'
$product->supplier; // Returns Supplier model
$product->supplier->name; // "Tech Supplies Inc"
$product->supplier->email; // "orders@techsupplies.com"Use Cases: Purchase order generation, reorder automation, supplier performance tracking
Brands
Product manufacturer or brand:
'brand_id' => '01hwbrd123abc456def789ghi0'
$product->brand; // Returns Brand model
$product->brand->name; // "Dell"
$product->brand->website; // "https://dell.com"Tags
Flexible multi-tag assignment:
// Assign tags (many-to-many)
$product->tags()->attach(['01hwtag100abc456def789ghi0', '01hwtag200abc456def789ghi0']);
// Tags during creation
'tags' => ['01hwtag100abc456def789ghi0', '01hwtag200abc456def789ghi0']
// Access tags
$product->tags; // Collection of Tag models
$product->tags->pluck('name'); // ["Bestseller", "New Arrival", "On Sale"]Use Cases: Product filtering, marketing segments, custom collections, dynamic product groups
Searching and Filtering
Full-Text Search
Search across product name, SKU, and description:
curl -X GET "https://api.crm.test/api/v1/operations/products/search?q=laptop" \
-H "Authorization: Bearer {token}"Searches: Product name, SKU, short description, long description, meta keywords
Filter by Status
curl -X GET "https://api.crm.test/api/v1/operations/products?status=active" \
-H "Authorization: Bearer {token}"Available Statuses: draft, active, inactive, discontinued, out_of_stock, pending_approval, rejected
Filter by Category / Supplier
# By category
curl -X GET "https://api.crm.test/api/v1/operations/products?category_id=01hwcat123abc456def789ghi0" \
-H "Authorization: Bearer {token}"
# By supplier
curl -X GET "https://api.crm.test/api/v1/operations/products?supplier_id=01hwsup123abc456def789ghi0" \
-H "Authorization: Bearer {token}"Price Range and Stock Filters
# Price range
curl -X GET "https://api.crm.test/api/v1/operations/products?min_price=500&max_price=1500" \
-H "Authorization: Bearer {token}"
# Low stock
curl -X GET "https://api.crm.test/api/v1/operations/products?stock_status=low" \
-H "Authorization: Bearer {token}"
# Out of stock
curl -X GET "https://api.crm.test/api/v1/operations/products?stock_status=out" \
-H "Authorization: Bearer {token}"Combined Filters
curl -X GET "https://api.crm.test/api/v1/operations/products?status=active&category_id=01hwcat123abc456def789ghi0&min_price=500&sort_by=price&sort_direction=desc&per_page=25" \
-H "Authorization: Bearer {token}"Bulk Operations
Bulk Status Update
Update status for multiple products:
curl -X POST https://api.crm.test/api/v1/operations/products/bulk-action \
-H "Authorization: Bearer {token}" \
-d '{
"action": "activate",
"product_ids": [
"01hwprd123abc456def789ghi0",
"01hwprd223abc456def789ghi0",
"01hwprd323abc456def789ghi0",
"01hwprd423abc456def789ghi0"
]
}'Available Actions: activate, deactivate, discontinue, delete
Bulk Export
Export products to CSV or Excel:
curl -X POST https://api.crm.test/api/v1/operations/products/export \
-H "Authorization: Bearer {token}" \
-d '{
"format": "csv",
"filters": {
"status": "active",
"category_id": 5
}
}'CQRS Examples
Commands (Write Operations)
All product modifications use commands:
// Create
$command = new CreateProductCommand(dto: $dto);
$product = $this->commandBus->dispatch($command);
// Update
$command = new UpdateProductCommand(productId: '01hwprd123abc456def789ghi0', dto: $dto);
$product = $this->commandBus->dispatch($command);
// Activate
$command = new ActivateProductCommand(productId: '01hwprd123abc456def789ghi0');
$product = $this->commandBus->dispatch($command);
// Deactivate
$command = new DeactivateProductCommand(productId: '01hwprd123abc456def789ghi0', reason: '...');
$product = $this->commandBus->dispatch($command);
// Discontinue
$command = new DiscontinueProductCommand(productId: '01hwprd123abc456def789ghi0', reason: '...');
$product = $this->commandBus->dispatch($command);
// Delete
$command = new DeleteProductCommand(productId: '01hwprd123abc456def789ghi0');
$this->commandBus->dispatch($command);Transaction Handling: CommandBus automatically wraps in database transaction.
Queries (Read Operations)
All product retrievals use queries:
// Get single product
$query = new GetProductByIdQuery(
productId: '01hwprd123abc456def789ghi0',
includes: ['category', 'supplier', 'brand', 'tags']
);
$product = $this->queryBus->ask($query);
// Get products with filters
$query = new GetProductsQuery(
filterDTO: ProductFilterDTO::fromArray([
'status' => 'active',
'category_id' => 5,
'min_price' => 500,
'max_price' => 1500,
'sort_by' => 'price',
'sort_direction' => 'desc',
'per_page' => 25,
])
);
$products = $this->queryBus->ask($query);
// Search products
$query = new SearchProductsQuery(
searchTerm: 'laptop',
filters: ['status' => 'active']
);
$results = $this->queryBus->ask($query);
// Featured products
$query = new GetFeaturedProductsQuery(
limit: 20
);
$featuredProducts = $this->queryBus->ask($query);No Transactions: Queries are read-only, no transaction wrapping.
Domain Events
Product operations emit domain events:
| Event | Trigger | Example Listeners |
|---|---|---|
ProductCreated | New product created | Sync to search index, notify catalog managers |
ProductUpdated | Product details changed | Update search index, invalidate cache |
ProductActivated | Product activated | Publish to catalog, notify sales team |
ProductDeactivated | Product deactivated | Remove from catalog, notify sales team |
ProductDiscontinued | Product discontinued | Archive product, update reporting |
ProductDeleted | Product deleted | Clean up related data, audit log |
API Endpoints
Product CRUD
| Method | Endpoint | Description |
|---|---|---|
| GET | /products | List products with filters/pagination |
| POST | /products | Create new product |
| GET | /products/{id} | Get single product details |
| PUT | /products/{id} | Update product |
| DELETE | /products/{id} | Delete product (soft delete) |
Product Lifecycle
| Method | Endpoint | Description |
|---|---|---|
| POST | /products/{id}/activate | Activate product |
| POST | /products/{id}/deactivate | Deactivate product |
| POST | /products/{id}/discontinue | Discontinue product |
Product Discovery
| Method | Endpoint | Description |
|---|---|---|
| GET | /products/search | Full-text search |
| GET | /products/catalog | Public catalog (active only) |
| GET | /products/featured | Featured products |
| GET | /products/requires-attention | Products needing attention |
| GET | /products/{id}/inventory | Product inventory across warehouses |
| GET | /products/{id}/analytics | Product analytics data |
Bulk Operations
| Method | Endpoint | Description |
|---|---|---|
| POST | /products/bulk-action | Bulk status update/delete |
| POST | /products/export | Export products to CSV/Excel |
See: API Reference for complete documentation.
Related Guides
- Product Lifecycle - Status transitions and lifecycle states
- Product Variants - Master/variant product management
- Pricing Strategy - Pricing configuration and margins
- Product Attributes - EAV custom properties
- Inventory Management - Stock tracking and reservations
- Inventory Overview - Multi-warehouse inventory
- Purchase Order Workflow - Supplier ordering process