Skip to content

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.

php
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.

php
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.

php
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.

php
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.

php
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.

php
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.

php
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:

php
[
    '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:

php
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.

php
[
    '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.

php
[
    '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:

php
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:

php
$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:

bash
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:

json
{
  "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

php
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:

php
$dto = UpdateProductDTO::fromArray([
    'selling_price' => 899.99,        // New price
    'stock_quantity' => 75,            // Update stock
    // Other fields remain unchanged
]);

API Request Example

bash
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

bash
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

bash
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

bash
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:

php
// 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:

php
'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:

php
'brand_id' => '01hwbrd123abc456def789ghi0'

$product->brand;  // Returns Brand model
$product->brand->name;  // "Dell"
$product->brand->website;  // "https://dell.com"

Tags

Flexible multi-tag assignment:

php
// 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

Search across product name, SKU, and description:

bash
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

bash
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

bash
# 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

bash
# 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

bash
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:

bash
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:

bash
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:

php
// 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:

php
// 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:

EventTriggerExample Listeners
ProductCreatedNew product createdSync to search index, notify catalog managers
ProductUpdatedProduct details changedUpdate search index, invalidate cache
ProductActivatedProduct activatedPublish to catalog, notify sales team
ProductDeactivatedProduct deactivatedRemove from catalog, notify sales team
ProductDiscontinuedProduct discontinuedArchive product, update reporting
ProductDeletedProduct deletedClean up related data, audit log

API Endpoints

Product CRUD

MethodEndpointDescription
GET/productsList products with filters/pagination
POST/productsCreate new product
GET/products/{id}Get single product details
PUT/products/{id}Update product
DELETE/products/{id}Delete product (soft delete)

Product Lifecycle

MethodEndpointDescription
POST/products/{id}/activateActivate product
POST/products/{id}/deactivateDeactivate product
POST/products/{id}/discontinueDiscontinue product

Product Discovery

MethodEndpointDescription
GET/products/searchFull-text search
GET/products/catalogPublic catalog (active only)
GET/products/featuredFeatured products
GET/products/requires-attentionProducts needing attention
GET/products/{id}/inventoryProduct inventory across warehouses
GET/products/{id}/analyticsProduct analytics data

Bulk Operations

MethodEndpointDescription
POST/products/bulk-actionBulk status update/delete
POST/products/exportExport products to CSV/Excel

See: API Reference for complete documentation.

Documentation for SynthesQ CRM/ERP Platform