laravel-shop/docs
Fabian @ Blax Software cbb4b84948 BF cart/pool/booking 2026-01-05 09:07:09 +01:00
..
ProductTypes BF cart/pool/booking 2026-01-05 09:07:09 +01:00
01-products.md A prompts, I docs/readme, BF orders, R tests locations 2025-12-30 09:29:43 +01:00
02-stripe.md A stripe & BFI cart 2025-11-28 10:24:07 +01:00
03-purchasing.md A prompts, I docs/readme, BF orders, R tests locations 2025-12-30 09:29:43 +01:00
04-stripe-checkout.md A prompts, I docs/readme, BF orders, R tests locations 2025-12-30 09:29:43 +01:00
05-product-relations.md A prompts, I docs/readme, BF orders, R tests locations 2025-12-30 09:29:43 +01:00
README.md AIBFR pool/booking/cart 2025-12-16 13:58:03 +01:00

README.md

Laravel Shop Documentation

Table of Contents

Product Types

Core Features

Quick Start

Understanding Booking Products

Booking products are time-based items that can be reserved for specific date ranges. Perfect for:

  • Hotel rooms
  • Rental equipment
  • Parking spaces
  • Event tickets
  • Service appointments

Learn more →

Understanding Pool Products

Pool products manage groups of individual items as a unified offering. Customers book from a "pool" and the system automatically assigns an available item. Ideal for:

  • Hotel room categories ("Standard Rooms")
  • Equipment fleets ("Rental Cars - Compact")
  • Parking facilities ("Parking Spaces")
  • General admission seating

Learn more →

Understanding Product Relations

The relation system enables complex product associations for marketing and structural purposes:

  • Marketing: Upsells, cross-sells, related products
  • Structural: Variations, bundles, pool/single items

Learn more →

Key Concepts

Stock Management

  • Booking Products: Track time-based availability with claims
  • Pool Products: Aggregate availability from single items
  • Claims: Temporary stock reservations (cart, bookings)

Pricing Strategies

Pool products support flexible pricing:

  • LOWEST (default): Cheapest available item
  • HIGHEST: Most expensive available item
  • AVERAGE: Average of available items

Prices are calculated from available items only, ensuring customers see accurate pricing.

Product Relations

Nine relation types for different purposes:

  • RELATED, UPSELL, CROSS_SELL, DOWNSELL, ADD_ON (marketing)
  • VARIATION, BUNDLE (structural)
  • SINGLE, POOL (special bidirectional for pool management)

Common Workflows

Creating a Booking Product

$room = Product::create([
    'name' => 'Deluxe Suite',
    'type' => ProductType::BOOKING,
    'manage_stock' => true,
]);

$room->increaseStock(5); // 5 rooms available

ProductPrice::create([
    'purchasable_id' => $room->id,
    'purchasable_type' => Product::class,
    'unit_amount' => 20000, // $200/day
    'is_default' => true,
]);

Creating a Pool Product

// 1. Create pool
$pool = Product::create([
    'name' => 'Parking Spaces',
    'type' => ProductType::POOL,
    'manage_stock' => false,
]);

// 2. Create single items
$spot1 = Product::create([
    'type' => ProductType::BOOKING,
    'manage_stock' => true,
]);
$spot1->increaseStock(1);

// 3. Link them
$pool->attachSingleItems([$spot1->id, $spot2->id, $spot3->id]);

// 4. Set pricing strategy
$pool->setPricingStrategy(PricingStrategy::LOWEST);

Booking a Product

$from = Carbon::parse('2025-01-15');
$until = Carbon::parse('2025-01-20');

// Check availability
if ($product->isAvailableForBooking($from, $until, $quantity = 1)) {
    // Add to cart (claims stock automatically)
    $cart->addToCart($product, 1, [], $from, $until);
}

Setting Up Product Relations

// Cross-sells
$laptop->productRelations()->attach([
    $mouse->id => ['type' => ProductRelationType::CROSS_SELL->value],
    $bag->id => ['type' => ProductRelationType::CROSS_SELL->value],
]);

// Upsells
$basicPlan->productRelations()->attach($premiumPlan->id, [
    'type' => ProductRelationType::UPSELL->value
]);

// Retrieve
$crossSells = $laptop->crossSellProducts;
$upsell = $basicPlan->upsellProducts->first();

Architecture Overview

Product Types

ProductType Enum:
├── SIMPLE      → Standard products
├── VARIABLE    → Products with variations
├── GROUPED     → Product groups
├── EXTERNAL    → External/affiliate products
├── BOOKING     → Time-based reservations ⭐
├── VARIATION   → Variant of a variable product
└── POOL        → Container for booking items ⭐

Relation Types

ProductRelationType Enum:
├── RELATED     → Similar products
├── UPSELL      → Premium alternatives
├── CROSS_SELL  → Complementary products
├── DOWNSELL    → Lower-priced alternatives
├── ADD_ON      → Optional extras
├── VARIATION   → Product variants
├── BUNDLE      → Package components
├── SINGLE      → Pool → Single items ⭐
└── POOL        → Single item → Pool ⭐

Stock System

Stock Flow:
├── INCREASE    → Add inventory (COMPLETED)
├── DECREASE    → Remove inventory (COMPLETED)
├── CLAIMED     → Reserve inventory (PENDING) ⭐
│   ├── Creates DECREASE entry (reduces available)
│   └── Creates CLAIMED entry (tracks reservation)
└── RETURN      → Return to inventory (COMPLETED)

Available Stock = Sum(COMPLETED entries) - Sum(active CLAIMS)

Best Practices

Booking Products

Always set manage_stock = true
Check availability before booking
Validate configuration with validateBookingConfiguration()
Handle date ranges properly (from < until)

Pool Products

Set pool manage_stock = false
Set single items manage_stock = true
Use attachSingleItems() for bidirectional relations
Validate with validatePoolConfiguration()
Set explicit pricing strategy

Relations

Use appropriate relation types semantically
Use helper methods (upsellProducts not manual queries)
Eager load to avoid N+1 queries
Add sort_order for display ordering

Troubleshooting

"Not enough stock available"

  • Check if stock was added: $product->increaseStock(5)
  • Check if stock is claimed by other bookings
  • Verify manage_stock = true

"Pool product has no single items"

  • Use attachSingleItems() to link items
  • Verify single items exist and have stock

Pool/Single relations not bidirectional

  • Must use attachSingleItems() not regular attach()
  • This creates both SINGLE and POOL relations automatically

Pricing shows wrong value

  • Check pricing strategy: $pool->getPricingStrategy()
  • Verify which items are available: getSingleItemsAvailability()
  • Remember: only available items are priced

Development Tips

Testing Bookings

// Create test booking product
$product = Product::factory()->create([
    'type' => ProductType::BOOKING,
    'manage_stock' => true,
]);
$product->increaseStock(10);

// Test availability
$this->assertTrue($product->isAvailableForBooking($from, $until, 5));

// Test booking
$cart->addToCart($product, 5, [], $from, $until);
$this->assertEquals(5, $product->getAvailableStock());

Testing Pools

// Create pool with items
$pool = Product::factory()->create(['type' => ProductType::POOL]);
$items = Product::factory()->count(3)->create([
    'type' => ProductType::BOOKING,
    'manage_stock' => true,
]);
$items->each->increaseStock(1);

$pool->attachSingleItems($items->pluck('id')->toArray());

// Test availability
$this->assertEquals(3, $pool->getPoolMaxQuantity($from, $until));

Debugging Relations

// Check relation exists
dd($product->productRelations()
    ->where('related_product_id', $otherId)
    ->where('type', ProductRelationType::RELATED->value)
    ->exists());

// Check bidirectional
dd($pool->singleProducts()->pluck('id'));
dd($singleItem->poolProducts()->pluck('id'));

Performance

Eager Loading

// Load all relations
Product::with([
    'singleProducts',
    'poolProducts',
    'crossSellProducts',
    'upsellProducts',
    'prices'
])->get();

// Load nested
Product::with('singleProducts.prices')->get();

Caching

// Cache availability calendars
Cache::remember("pool:{$id}:availability", 3600, function() {
    return $pool->getPoolAvailabilityCalendar($from, $until);
});

// Cache pricing
Cache::remember("pool:{$id}:price", 3600, function() {
    return $pool->getCurrentPrice();
});

API Examples

Get Booking Availability

GET /api/products/{id}/availability?from=2025-01-15&until=2025-01-20

Response:
{
    "available": true,
    "max_quantity": 5,
    "calendar": {
        "2025-01-15": 5,
        "2025-01-16": 3,
        ...
    }
}

Get Pool Information

GET /api/products/{id}/pool-details

Response:
{
    "pool": {
        "id": 1,
        "name": "Parking Spaces",
        "pricing_strategy": "lowest"
    },
    "single_items": [
        {"id": 10, "name": "Spot 1", "available": 1},
        {"id": 11, "name": "Spot 2", "available": 0},
        {"id": 12, "name": "Spot 3", "available": 1}
    ],
    "price_range": {
        "min": 2000,
        "max": 3000
    }
}

Contributing

When adding new features:

  1. Update relevant documentation
  2. Add test cases for booking/pool scenarios
  3. Consider stock management implications
  4. Validate relation logic

Support

For issues or questions:

  • Check troubleshooting sections
  • Review test cases for examples
  • See related documentation for context