Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions code_samples/api/product_catalog/src/Command/ProductCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Command;

use App\ProductCatalog\Availability\PurchasableWithoutStockAvailabilityContext;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\ProductCatalog\Local\LocalProductServiceInterface;
Expand Down Expand Up @@ -98,26 +99,34 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$product = $this->productService->getProduct('NEWMODIFIEDPRODUCT');

$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, true, true);
$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, false, true);

$this->productAvailabilityService->createProductAvailability($productAvailabilityCreateStruct);

if ($this->productAvailabilityService->hasAvailability($product)) {
$availability = $this->productAvailabilityService->getAvailability($product);

$output->write($availability->isAvailable() ? 'Available' : 'Unavailable');
$output->writeln(' with stock ' . $availability->getStock());

$availability = $this->productAvailabilityService->getAvailability($product);
$output->writeln($availability->getAvailability() ? 'Available flag: true' : 'Available flag: false');
$output->writeln($availability->getComputedAvailability() ? 'Can be ordered: true' : 'Can be ordered: false');
$output->writeln('Stock: ' . $availability->getStock());

$productAvailabilityUpdateStruct = new ProductAvailabilityUpdateStruct($product, true, false, 80);

$this->productAvailabilityService->updateProductAvailability($productAvailabilityUpdateStruct);

$output->write($availability->isAvailable() ? 'Available' : 'Unavailable');
$output->writeln($availability->getAvailability() ? 'Available flag: true' : 'Available flag: false');
$output->writeln($availability->getComputedAvailability() ? 'Can be ordered: true' : 'Can be ordered: false');
$output->writeln(' available now with stock ' . $availability->getStock());
}

$availability = $this->productAvailabilityService->getAvailability(
$product,
new PurchasableWithoutStockAvailabilityContext()
);

$canBeOrdered = $availability->getComputedAvailability();
$output->writeln('Can be ordered: ' . ($canBeOrdered ? 'true' : 'false') . ', Stock: ' . $availability->getStock());

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

return self::SUCCESS;
Expand Down
4 changes: 4 additions & 0 deletions code_samples/pim/availability/config/custom_services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
App\ProductCatalog\Availability\ProductAvailabilityPurchasableWithoutStockStrategy:
tags:
- { name: ibexa.product_catalog.availability.strategy }
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php declare(strict_types=1);

namespace App\ProductCatalog\Availability;

use Ibexa\Contracts\ProductCatalog\ProductAvailabilityStrategyInterface;
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityInterface;
use Ibexa\Contracts\ProductCatalog\Values\ProductInterface;
use Ibexa\ProductCatalog\Local\Persistence\Legacy\ProductAvailability\HandlerInterface;
use Ibexa\ProductCatalog\Local\Repository\Values\Availability;

final class ProductAvailabilityPurchasableWithoutStockStrategy implements ProductAvailabilityStrategyInterface
{
private HandlerInterface $handler;

public function __construct(HandlerInterface $handler)
{
$this->handler = $handler;
}

public function accept(AvailabilityContextInterface $context): bool
{
return $context instanceof PurchasableWithoutStockAvailabilityContext;
}

public function getProductAvailability(
ProductInterface $product,
AvailabilityContextInterface $context
): AvailabilityInterface {
$productAvailability = $this->handler->find($product->getCode());

$rawAvailableFlag = $productAvailability->isAvailable();
$stock = $productAvailability->getStock();
$isInfinite = $productAvailability->isInfinite();

$computedAvailable = $this->calculateAvailability(
$rawAvailableFlag,
$stock,
$isInfinite,
);

return new Availability(
$product,
$rawAvailableFlag,
$computedAvailable,
$isInfinite,
$stock,
);
}

private function calculateAvailability(

Check warning on line 51 in code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This method has 4 returns, which is more than the 3 allowed.

See more on https://sonarcloud.io/project/issues?id=ezsystems_developer-documentation&issues=AZ4HDHPjNpq4orfx7oMA&open=AZ4HDHPjNpq4orfx7oMA&pullRequest=3190
bool $rawAvailable,
?int $stock,
bool $isInfinite
): bool {
if ($rawAvailable === false) {
return false;
}

if ($isInfinite) {
return true;
}

if ($stock === null) {
return true;
}

return $stock >= 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);

namespace App\ProductCatalog\Availability;

use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;

final class PurchasableWithoutStockAvailabilityContext implements AvailabilityContextInterface
{
}
53 changes: 53 additions & 0 deletions docs/pim/create_custom_availability_strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: Implement custom availability strategies to handle different business scenarios, for example pre-orders or per-region availability.
---

# Create custom availability strategy

The product catalog uses an availability strategy to calculate [computed availability](products.md#availability-and-computed-availability) for a product, deciding whether the customers can order it.
The default is based on the product availability and stock amount.

Check notice on line 8 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L8

[Ibexa.Passive] Try to avoid passive tense, when possible.
Raw output
{"message": "[Ibexa.Passive] Try to avoid passive tense, when possible.", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 8, "column": 13}}}, "severity": "INFO"}

You can replace this logic with a custom strategy to handle specific business scenarios, for example preorders, minimum order quantities, or per-region availability.

Check failure on line 10 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L10

[Ibexa.Spellcheck] Did you really mean 'preorders'?
Raw output
{"message": "[Ibexa.Spellcheck] Did you really mean 'preorders'?", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 10, "column": 102}}}, "severity": "ERROR"}

The following example implements an availability strategy which allows buying products when they're set as available, without taking their stock into account.
You could use it for [virtual products](products.md#product-types) or in preorder scenarios.

Check failure on line 13 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L13

[Ibexa.Spellcheck] Did you really mean 'preorder'?
Raw output
{"message": "[Ibexa.Spellcheck] Did you really mean 'preorder'?", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 13, "column": 74}}}, "severity": "ERROR"}

## Create custom availability context

Use an availability context to pass the parameters needed by the strategy to evaluate computed availability.
To do it, create a class implementing the [`AvailabilityContextInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityContextInterface.html) interface:

``` php
[[= include_file('code_samples/pim/availability/src/PurchasableWithoutStockAvailabilityContext.php') =]]
```

## Create custom availability strategy

Create a class implementing [`ProductAvailabilityStrategyInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductAvailabilityStrategyInterface.html):

``` php
[[= include_file('code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php') =]]
```

The strategy has two methods:

- `accept()` decides if the strategy can handle the provided availability context
- `getProductAvailability()` returns an [`AvailabilityInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityInterface.html) object

When constructing the `AvailabilityInterface` object, provide the stock amount, the availability flag, and the result of your custom availability logic.

## Register strategy as a service

Check notice on line 39 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L39

[Ibexa.ArticlesInHeadings] Avoid articles in headings.
Raw output
{"message": "[Ibexa.ArticlesInHeadings] Avoid articles in headings.", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 39, "column": 25}}}, "severity": "INFO"}

If you're not using [autowiring]([[= symfony_doc =]]/service_container/autowiring.html), tag the strategy service with `ibexa.product_catalog.availability.strategy`:

Check notice on line 41 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L41

[Ibexa.ByUsing] Prefer 'by using' or 'with' to plain 'using'.
Raw output
{"message": "[Ibexa.ByUsing] Prefer 'by using' or 'with' to plain 'using'.", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 41, "column": 11}}}, "severity": "INFO"}

``` yaml
[[= include_file('code_samples/pim/availability/config/custom_services.yaml') =]]
```

## Use custom context

To evaluate product availability using a custom strategy, pass the custom context as the second argument to [`ProductAvailabilityServiceInterface::getAvailability()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductAvailabilityServiceInterface.html):

Check notice on line 49 in docs/pim/create_custom_availability_strategy.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/pim/create_custom_availability_strategy.md#L49

[Ibexa.ByUsing] Prefer 'by using' or 'with' to plain 'using'.
Raw output
{"message": "[Ibexa.ByUsing] Prefer 'by using' or 'with' to plain 'using'.", "location": {"path": "docs/pim/create_custom_availability_strategy.md", "range": {"start": {"line": 49, "column": 21}}}, "severity": "INFO"}

```php
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 122, 127, remove_indent=True) =]]
```
1 change: 1 addition & 0 deletions docs/pim/customize_pim.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ You can customize various areas of the Product Information Management solution t
"pim/create_product_code_generator",
"pim/create_custom_catalog_filter",
"pim/create_custom_name_schema_strategy",
"pim/create_custom_availability_strategy",
], columns=4) =]]
Loading
Loading