Skip to content

Expanded the product availability section#3190

Open
mnocon wants to merge 7 commits into4.6from
availability
Open

Expanded the product availability section#3190
mnocon wants to merge 7 commits into4.6from
availability

Conversation

@mnocon
Copy link
Copy Markdown
Contributor

@mnocon mnocon commented May 8, 2026

Jira: https://ibexa.atlassian.net/browse/IBX-11478

Doc for https://github.com/ibexa/product-catalog/pull/1488

Target: 4.6, 5.0

This PR expands the availability section to explain the difference between availability and computed availability.

In addition, creating custom availability strategies is added

Code tested locally:
Screenshot 2026-05-08 at 12 39 24

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

@mnocon mnocon marked this pull request as draft May 8, 2026 09:54
@mnocon mnocon marked this pull request as ready for review May 8, 2026 10:29
@mnocon mnocon requested review from micszo and mikadamczyk May 8, 2026 10:45
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/api/product_catalog/src/Command/ProductCommand.php


code_samples/api/product_catalog/src/Command/ProductCommand.php

docs/pim/create_custom_availability_strategy.md@51:```php
docs/pim/create_custom_availability_strategy.md@52:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 122, 127, remove_indent=True) =]]
docs/pim/create_custom_availability_strategy.md@53:```

001⫶$availability = $this->productAvailabilityService->getAvailability(
002⫶ $product,
003⫶ new PurchasableWithoutStockAvailabilityContext()
004⫶);
005⫶
006⫶$canBeOrdered = $availability->getComputedAvailability();

docs/pim/product_api.md@23:``` php
docs/pim/product_api.md@23:``` php
docs/pim/product_api.md@24:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 68, 71) =]]
docs/pim/product_api.md@24:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 70, 72, remove_indent=True) =]]
docs/pim/product_api.md@25:```

docs/pim/product_api.md@25:```

001⫶        $product = $this->productService->getProduct($productCode);
001⫶$product = $this->productService->getProduct($productCode);
002⫶
002⫶
003⫶        $output->writeln('Product with code ' . $product->getCode() . ' is ' . $product->getName());
003⫶$output->writeln('Product with code ' . $product->getCode() . ' is ' . $product->getName());

docs/pim/product_api.md@31:``` php

docs/pim/product_api.md@31:``` php
docs/pim/product_api.md@32:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 72, 82) =]]
docs/pim/product_api.md@32:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 74, 83, remove_indent=True) =]]
docs/pim/product_api.md@33:```

docs/pim/product_api.md@33:```

001⫶        $criteria = new Criterion\ProductType([$productType]);
002⫶ $sortClauses = [new SortClause\ProductName(ProductQuery::SORT_ASC)];
001⫶$criteria = new Criterion\ProductType([$productType]);
002⫶$sortClauses = [new SortClause\ProductName(ProductQuery::SORT_ASC)];
003⫶
003⫶
004⫶        $productQuery = new ProductQuery(null, $criteria, $sortClauses);
004⫶$productQuery = new ProductQuery(null, $criteria, $sortClauses);
005⫶
005⫶
006⫶        $products = $this->productService->findProducts($productQuery);
006⫶$products = $this->productService->findProducts($productQuery);
007⫶
007⫶
008⫶        foreach ($products as $product) {
009⫶ $output->writeln($product->getName() . ' of type ' . $product->getProductType()->getName());
010⫶ }
008⫶foreach ($products as $product) {
009⫶ $output->writeln($product->getName() . ' of type ' . $product->getProductType()->getName());
010⫶}

docs/pim/product_api.md@41:``` php

docs/pim/product_api.md@41:``` php
docs/pim/product_api.md@42:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 93, 97) =]]
docs/pim/product_api.md@42:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 95, 98, remove_indent=True) =]]
docs/pim/product_api.md@43:```

docs/pim/product_api.md@43:```

001⫶        $productUpdateStruct = $this->localProductService->newProductUpdateStruct($product);
002⫶ $productUpdateStruct->setCode('NEWMODIFIEDPRODUCT');
001⫶$productUpdateStruct = $this->localProductService->newProductUpdateStruct($product);
002⫶$productUpdateStruct->setCode('NEWMODIFIEDPRODUCT');
003⫶
003⫶
004⫶        $this->localProductService->updateProduct($productUpdateStruct);
004⫶$this->localProductService->updateProduct($productUpdateStruct);

docs/pim/product_api.md@49:``` php

docs/pim/product_api.md@49:``` php
docs/pim/product_api.md@50:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 83, 90) =]]
docs/pim/product_api.md@50:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 85, 89, remove_indent=True) =]]
docs/pim/product_api.md@51:```

docs/pim/product_api.md@51:```

001⫶        $productType = $this->productTypeService->getProductType($productType);
001⫶$productType = $this->productTypeService->getProductType($productType);
002⫶
002⫶
003⫶        $createStruct = $this->localProductService->newProductCreateStruct($productType, 'eng-GB');
004⫶ $createStruct->setCode('NEWPRODUCT');
005⫶ $createStruct->setField('name', 'New Product');
006⫶
007⫶ $this->localProductService->createProduct($createStruct);
003⫶$createStruct = $this->localProductService->newProductCreateStruct($productType, 'eng-GB');
004⫶$createStruct->setCode('NEWPRODUCT');
005⫶$createStruct->setField('name', 'New Product');

docs/pim/product_api.md@55:``` php

docs/pim/product_api.md@55:``` php
docs/pim/product_api.md@56:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 120, 121) =]]
docs/pim/product_api.md@56:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 130, 130, remove_indent=True) =]]
docs/pim/product_api.md@57:```

docs/pim/product_api.md@57:```

001⫶        $this->localProductService->deleteProduct($product);
001⫶$this->localProductService->deleteProduct($product);


docs/pim/product_api.md@189:```php
docs/pim/product_api.md@190:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 104, 109) =]] }
docs/pim/product_api.md@191:```
docs/pim/product_api.md@196:``` php
docs/pim/product_api.md@197:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 106, 111, remove_indent=True) =]]
docs/pim/product_api.md@198:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 134, 134, remove_indent=True) =]]
docs/pim/product_api.md@199:```


001⫶        if ($this->productAvailabilityService->hasAvailability($product)) {
002⫶ $availability = $this->productAvailabilityService->getAvailability($product);
001⫶if ($this->productAvailabilityService->hasAvailability($product)) {
002⫶ $availability = $this->productAvailabilityService->getAvailability($product);
003⫶
003⫶
004⫶            $output->write($availability->isAvailable() ? 'Available' : 'Unavailable');
005⫶ $output->writeln(' with stock ' . $availability->getStock());
006⫶ }
004⫶    $output->writeln($availability->getAvailability() ? 'Available flag: true' : 'Available flag: false');
005⫶ $output->writeln($availability->getComputedAvailability() ? 'Can be ordered: true' : 'Can be ordered: false');
006⫶ $output->writeln('Stock: ' . $availability->getStock());
007⫶}

docs/pim/product_api.md@203:``` php
docs/pim/product_api.md@204:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 122, 128, remove_indent=True) =]]
docs/pim/product_api.md@205:```

001⫶$availability = $this->productAvailabilityService->getAvailability(
002⫶ $product,
003⫶ new PurchasableWithoutStockAvailabilityContext()
004⫶);
005⫶
006⫶$canBeOrdered = $availability->getComputedAvailability();
007⫶$output->writeln('Can be ordered: ' . ($canBeOrdered ? 'true' : 'false') . ', Stock: ' . $availability->getStock());


docs/pim/product_api.md@196:``` php
docs/pim/product_api.md@197:[[= include_file('code_samples/api/product_catalog/src/Command/ProductCommand.php', 112, 115) =]]
docs/pim/product_api.md@198:```
docs/pim/product_api.md@210:``` php
docs/pim/product_api.md@211:[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 113, 115, remove_indent=True) =]]
docs/pim/product_api.md@212:```


001⫶            $productAvailabilityUpdateStruct = new ProductAvailabilityUpdateStruct($product, true, false, 80);
001⫶$productAvailabilityUpdateStruct = new ProductAvailabilityUpdateStruct($product, true, false, 80);
002⫶
002⫶
003⫶            $this->productAvailabilityService->updateProductAvailability($productAvailabilityUpdateStruct);
003⫶$this->productAvailabilityService->updateProductAvailability($productAvailabilityUpdateStruct);


code_samples/pim/availability/config/custom_services.yaml



code_samples/pim/availability/config/custom_services.yaml

docs/pim/create_custom_availability_strategy.md@43:``` yaml
docs/pim/create_custom_availability_strategy.md@44:[[= include_file('code_samples/pim/availability/config/custom_services.yaml') =]]
docs/pim/create_custom_availability_strategy.md@45:```

001⫶services:
002⫶ App\ProductCatalog\Availability\ProductAvailabilityPurchasableWithoutStockStrategy:
003⫶ tags:
004⫶ - { name: ibexa.product_catalog.availability.strategy }


code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php


code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php

docs/pim/create_custom_availability_strategy.md@28:``` php
docs/pim/create_custom_availability_strategy.md@29:[[= include_file('code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php') =]]
docs/pim/create_custom_availability_strategy.md@30:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\ProductCatalog\Availability;
004⫶
005⫶use Ibexa\Contracts\ProductCatalog\ProductAvailabilityStrategyInterface;
006⫶use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
007⫶use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityInterface;
008⫶use Ibexa\Contracts\ProductCatalog\Values\ProductInterface;
009⫶use Ibexa\ProductCatalog\Local\Persistence\Legacy\ProductAvailability\HandlerInterface;
010⫶use Ibexa\ProductCatalog\Local\Repository\Values\Availability;
011⫶
012⫶final class ProductAvailabilityPurchasableWithoutStockStrategy implements ProductAvailabilityStrategyInterface
013⫶{
014⫶ private HandlerInterface $handler;
015⫶
016⫶ public function __construct(HandlerInterface $handler)
017⫶ {
018⫶ $this->handler = $handler;
019⫶ }
020⫶
021⫶ public function accept(AvailabilityContextInterface $context): bool
022⫶ {
023⫶ return $context instanceof PurchasableWithoutStockAvailabilityContext;
024⫶ }
025⫶
026⫶ public function getProductAvailability(
027⫶ ProductInterface $product,
028⫶ AvailabilityContextInterface $context
029⫶ ): AvailabilityInterface {
030⫶ $productAvailability = $this->handler->find($product->getCode());
031⫶
032⫶ $rawAvailableFlag = $productAvailability->isAvailable();
033⫶ $stock = $productAvailability->getStock();
034⫶ $isInfinite = $productAvailability->isInfinite();
035⫶
036⫶ $computedAvailable = $this->calculateAvailability(
037⫶ $rawAvailableFlag,
038⫶ $stock,
039⫶ $isInfinite,
040⫶ );
041⫶
042⫶ return new Availability(
043⫶ $product,
044⫶ $rawAvailableFlag,
045⫶ $computedAvailable,
046⫶ $isInfinite,
047⫶ $stock,
048⫶ );
049⫶ }
050⫶
051⫶ private function calculateAvailability(
052⫶ bool $rawAvailable,
053⫶ ?int $stock,
054⫶ bool $isInfinite
055⫶ ): bool {
056⫶ if ($rawAvailable === false) {
057⫶ return false;
058⫶ }
059⫶
060⫶ if ($isInfinite) {
061⫶ return true;
062⫶ }
063⫶
064⫶ if ($stock === null) {
065⫶ return true;
066⫶ }
067⫶
068⫶ return $stock >= 0;
069⫶ }
070⫶}


code_samples/pim/availability/src/PurchasableWithoutStockAvailabilityContext.php


code_samples/pim/availability/src/PurchasableWithoutStockAvailabilityContext.php

docs/pim/create_custom_availability_strategy.md@20:``` php
docs/pim/create_custom_availability_strategy.md@21:[[= include_file('code_samples/pim/availability/src/PurchasableWithoutStockAvailabilityContext.php') =]]
docs/pim/create_custom_availability_strategy.md@22:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\ProductCatalog\Availability;
004⫶
005⫶use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
006⫶
007⫶final class PurchasableWithoutStockAvailabilityContext implements AvailabilityContextInterface
008⫶{
009⫶}

Download colorized diff

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant