Skip to content

Commit dc929b2

Browse files
mnocondabrt
andauthored
Expanded the product availability section (#3190)
* Expanded product availability * Adapted to PHP 7,4 --------- Co-authored-by: Tomasz Dąbrowski <64841871+dabrt@users.noreply.github.com>
1 parent 8abad57 commit dc929b2

10 files changed

Lines changed: 225 additions & 46 deletions

File tree

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Command;
44

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

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

101-
$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, true, true);
102+
$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, false, true);
102103

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

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

108-
$output->write($availability->isAvailable() ? 'Available' : 'Unavailable');
109-
$output->writeln(' with stock ' . $availability->getStock());
110-
111-
$availability = $this->productAvailabilityService->getAvailability($product);
109+
$output->writeln($availability->getAvailability() ? 'Available flag: true' : 'Available flag: false');
110+
$output->writeln($availability->getComputedAvailability() ? 'Can be ordered: true' : 'Can be ordered: false');
111+
$output->writeln('Stock: ' . $availability->getStock());
112112

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

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

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

122+
$availability = $this->productAvailabilityService->getAvailability(
123+
$product,
124+
new PurchasableWithoutStockAvailabilityContext()
125+
);
126+
127+
$canBeOrdered = $availability->getComputedAvailability();
128+
$output->writeln('Can be ordered: ' . ($canBeOrdered ? 'true' : 'false') . ', Stock: ' . $availability->getStock());
129+
121130
$this->localProductService->deleteProduct($product);
122131

123132
return self::SUCCESS;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
services:
2+
App\ProductCatalog\Availability\ProductAvailabilityPurchasableWithoutStockStrategy:
3+
tags:
4+
- { name: ibexa.product_catalog.availability.strategy }
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\ProductCatalog\Availability;
4+
5+
use Ibexa\Contracts\ProductCatalog\ProductAvailabilityStrategyInterface;
6+
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
7+
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityInterface;
8+
use Ibexa\Contracts\ProductCatalog\Values\ProductInterface;
9+
use Ibexa\ProductCatalog\Local\Persistence\Legacy\ProductAvailability\HandlerInterface;
10+
use Ibexa\ProductCatalog\Local\Repository\Values\Availability;
11+
12+
final class ProductAvailabilityPurchasableWithoutStockStrategy implements ProductAvailabilityStrategyInterface
13+
{
14+
private HandlerInterface $handler;
15+
16+
public function __construct(HandlerInterface $handler)
17+
{
18+
$this->handler = $handler;
19+
}
20+
21+
public function accept(AvailabilityContextInterface $context): bool
22+
{
23+
return $context instanceof PurchasableWithoutStockAvailabilityContext;
24+
}
25+
26+
public function getProductAvailability(
27+
ProductInterface $product,
28+
AvailabilityContextInterface $context
29+
): AvailabilityInterface {
30+
$productAvailability = $this->handler->find($product->getCode());
31+
32+
$rawAvailableFlag = $productAvailability->isAvailable();
33+
$stock = $productAvailability->getStock();
34+
$isInfinite = $productAvailability->isInfinite();
35+
36+
$computedAvailable = $this->calculateAvailability(
37+
$rawAvailableFlag,
38+
$stock,
39+
$isInfinite,
40+
);
41+
42+
return new Availability(
43+
$product,
44+
$rawAvailableFlag,
45+
$computedAvailable,
46+
$isInfinite,
47+
$stock,
48+
);
49+
}
50+
51+
private function calculateAvailability(
52+
bool $rawAvailable,
53+
?int $stock,
54+
bool $isInfinite
55+
): bool {
56+
if ($rawAvailable === false) {
57+
return false;
58+
}
59+
60+
if ($isInfinite) {
61+
return true;
62+
}
63+
64+
if ($stock === null) {
65+
return true;
66+
}
67+
68+
return $stock >= 0;
69+
}
70+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\ProductCatalog\Availability;
4+
5+
use Ibexa\Contracts\ProductCatalog\Values\Availability\AvailabilityContextInterface;
6+
7+
final class PurchasableWithoutStockAvailabilityContext implements AvailabilityContextInterface
8+
{
9+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
description: Implement custom availability strategies to handle different business scenarios, for example pre-orders or per-region availability.
3+
---
4+
5+
# Create custom availability strategy
6+
7+
The product catalog uses an availability strategy to calculate [computed availability](products.md#availability-and-computed-availability) for a product.
8+
Computed availability decides whether the customers can order the product.
9+
The default availability strategy is based on the product availability and stock amount.
10+
11+
You can replace this logic with a custom strategy to handle specific business scenarios, for example preorders, minimum order quantities, or per-region availability.
12+
13+
The following example implements an availability strategy which allows buying products when they're set as available, without taking their stock into account.
14+
You could use it for [virtual products](products.md#product-types) or in preorder scenarios.
15+
16+
## Create custom availability context
17+
18+
Use an availability context to pass the parameters needed by the strategy to evaluate computed availability.
19+
To do it, create a class that implements the [`AvailabilityContextInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityContextInterface.html) interface:
20+
21+
``` php
22+
[[= include_file('code_samples/pim/availability/src/PurchasableWithoutStockAvailabilityContext.php') =]]
23+
```
24+
25+
## Create custom availability strategy
26+
27+
Create a class that implements the [`ProductAvailabilityStrategyInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductAvailabilityStrategyInterface.html) interface:
28+
29+
``` php
30+
[[= include_file('code_samples/pim/availability/src/ProductAvailabilityPurchasableWithoutStockStrategy.php') =]]
31+
```
32+
33+
The strategy has two methods:
34+
35+
- `accept()` decides if the strategy can handle the provided availability context
36+
- `getProductAvailability()` returns an [`AvailabilityInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityInterface.html) object
37+
38+
When constructing the `AvailabilityInterface` object, provide the stock amount, the availability flag, and the result of your custom availability logic.
39+
40+
## Register strategy as a service
41+
42+
If you're not using [autowiring]([[= symfony_doc =]]/service_container/autowiring.html), tag the strategy service with `ibexa.product_catalog.availability.strategy`:
43+
44+
``` yaml
45+
[[= include_file('code_samples/pim/availability/config/custom_services.yaml') =]]
46+
```
47+
48+
## Use custom context
49+
50+
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):
51+
52+
```php
53+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 122, 127, remove_indent=True) =]]
54+
```

docs/pim/customize_pim.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ You can customize various areas of the Product Information Management solution t
1212
"pim/create_product_code_generator",
1313
"pim/create_custom_catalog_filter",
1414
"pim/create_custom_name_schema_strategy",
15+
"pim/create_custom_availability_strategy",
1516
], columns=4) =]]

0 commit comments

Comments
 (0)