Skip to content

Commit 9939df8

Browse files
mnocondabrt
andcommitted
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 793faee commit 9939df8

10 files changed

Lines changed: 206 additions & 28 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;
@@ -84,26 +85,34 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8485

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

87-
$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, true, true);
88+
$productAvailabilityCreateStruct = new ProductAvailabilityCreateStruct($product, false, true);
8889

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

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

94-
$output->write($availability->isAvailable() ? 'Available' : 'Unavailable');
95-
$output->writeln(' with stock ' . $availability->getStock());
96-
97-
$availability = $this->productAvailabilityService->getAvailability($product);
95+
$output->writeln($availability->getAvailability() ? 'Available flag: true' : 'Available flag: false');
96+
$output->writeln($availability->getComputedAvailability() ? 'Can be ordered: true' : 'Can be ordered: false');
97+
$output->writeln('Stock: ' . $availability->getStock());
9898

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

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

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

108+
$availability = $this->productAvailabilityService->getAvailability(
109+
$product,
110+
new PurchasableWithoutStockAvailabilityContext()
111+
);
112+
113+
$canBeOrdered = $availability->getComputedAvailability();
114+
$output->writeln('Can be ordered: ' . ($canBeOrdered ? 'true' : 'false') . ', Stock: ' . $availability->getStock());
115+
107116
$this->localProductService->deleteProduct($product);
108117

109118
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/product_catalog/customize_product_catalog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ You can customize various areas of the product catalog capabilities to adjust it
1414
"product_catalog/create_custom_name_schema_strategy",
1515
"product_catalog/customize_product_attribute_templates",
1616
"product_catalog/customize_product_embed_templates",
17+
"product_catalog/create_custom_availability_strategy",
1718
], columns=4) =]]

docs/product_catalog/product_api.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Use PHP API to manage products in PIM, their attributes, availability and prices.
2+
description: Use PHP API to manage products in PIM, their attributes, availability, and prices.
33
month_change: false
44
---
55

@@ -23,15 +23,15 @@ month_change: false
2323
Get an individual product by using the `ProductServiceInterface::getProduct()` method:
2424

2525
``` php
26-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 55, 57, remove_indent=True) =]]
26+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 56, 58, remove_indent=True) =]]
2727
```
2828

2929
Find multiple products with `ProductServiceInterface::findProducts()`.
3030

3131
Provide the method with optional filter, query or Sort Clauses.
3232

3333
``` php
34-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 59, 68, remove_indent=True) =]]
34+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 60, 69, remove_indent=True) =]]
3535
```
3636

3737
See [Product Search Criteria](product_search_criteria.md) and [Product Sort Clauses](product_sort_clauses.md) references for more information about how to use the [`ProductQuery`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-ProductQuery.html) class.
@@ -41,21 +41,21 @@ See [Product Search Criteria](product_search_criteria.md) and [Product Sort Clau
4141
To create, update and delete products, use the `LocalProductServiceInterface`.
4242

4343
``` php
44-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 80, 83, remove_indent=True) =]]
44+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 81, 84, remove_indent=True) =]]
4545
```
4646

4747
To create a product, use `LocalProductServiceInterface::newProductCreateStruct()` to get a [`ProductCreateStruct`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-Values-Product-ProductCreateStruct.html).
4848
Provide the method with the product type object and the main language code.
4949
You also need to set (at least) the code for the product and the required Field of the underlying content type, `name`:
5050

5151
``` php
52-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 70, 76, remove_indent=True) =]]
52+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 71, 77, remove_indent=True) =]]
5353
```
5454

5555
To delete a product, use `LocalProductServiceInterface::deleteProduct()`:
5656

5757
``` php
58-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 107, 107, remove_indent=True) =]]
58+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 116, 116, remove_indent=True) =]]
5959
```
6060

6161
### Product variants
@@ -214,17 +214,30 @@ You can also get a list of product types with `ProductTypeServiceInterface::find
214214

215215
## Product availability
216216

217-
Product availability is an object which defines whether a product is available, and if so, in what stock.
217+
Product availability is an object which defines whether a product is set as available, in what stock, and whether it can be ordered.
218218
To manage it, use [`ProductAvailabilityServiceInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-ProductAvailabilityServiceInterface.html).
219219

220-
To check whether a product is available (with or without stock defined), use `ProductAvailabilityServiceInterface::hasAvailability()`.
220+
The [`AvailabilityInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityInterface.html) provides two distinct availability values:
221221

222-
Get the availability object with `ProductAvailabilityServiceInterface::getAvailability()`.
223-
You can then use `ProductAvailabilityServiceInterface::getStock()` to get the stock number for the product:
222+
- `getAvailability()` returns the value of availability flag as set for the product
223+
- `getComputedAvailability()` returns whether the product can be ordered
224224

225-
```php
226-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 91, 95, remove_indent=True) =]]
227-
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 105, 105, remove_indent=True) =]]
225+
For more information about the distinction between these two values, see [Availability and computed availability](products.md#availability-and-computed-availability).
226+
227+
To check whether a product is set as available, use `ProductAvailabilityServiceInterface::hasAvailability()`.
228+
229+
You can get the availability object with `ProductAvailabilityServiceInterface::getAvailability()`.
230+
The returned object contains both the stored and computed availability:
231+
232+
``` php
233+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 92, 97, remove_indent=True) =]]
234+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 120, 120, remove_indent=True) =]]
235+
```
236+
237+
To evaluate computed availability for a [specific context](create_custom_availability_strategy.md), for example, a specific requested quantity or customer group, pass an optional [`AvailabilityContextInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-AvailabilityContextInterface.html) object as the second argument:
238+
239+
``` php
240+
[[= include_code('code_samples/api/product_catalog/src/Command/ProductCommand.php', 108, 114, remove_indent=True) =]]
228241
```
229242

230243
To change availability for a product, use `ProductAvailabilityServiceInterface::updateProductAvailability()` with a [`ProductAvailabilityUpdateStruct`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Availability-ProductAvailabilityUpdateStruct.html) and provide it with the product object.

docs/product_catalog/products.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,24 @@ You set product availability per variant or per base product:
9696
- if a product cannot have variants (has no attributes with the "Used for product variants" flag), you set availability per base product
9797
- if a product can have variants (even if no variants are configured yet), you set availability per variant.
9898

99-
When a product is available, it can have numerical stock defined.
99+
When a product is set as available, it can have numerical stock defined.
100100
The stock can also be set to infinite (for example, in case of digital products).
101101

102-
!!! note
102+
### Availability and computed availability
103103

104-
Availability doesn't automatically mean that a product can be ordered.
105-
A product can be available, but have zero stock.
104+
Setting a product as available doesn't automatically mean that it can be ordered.
105+
For example, a product can be set as available, but have zero stock.
106106

107-
A product can only be ordered when it has either positive stock, or stock set to infinite.
107+
The product catalog distinguishes between two types of availability:
108+
109+
- Availability as a value set per product or variant
110+
111+
Availability represents whether the product was set as **Available**, for example in the [back office **Availability** tab]([[= user_doc =]]/product_catalog/manage_availability_and_stock/#set-product-availability) or [PHP API](product_api.md#product-availability).
112+
113+
- Computed availability
114+
115+
Computed availability represents whether the product can actually be ordered.
116+
By default, a product can only be ordered when it's set as available and has either positive or infinite stock.
117+
118+
You can implement a custom strategy to handle different selling scenarios, such as minimum order quantity, minimum stock quantity, or region-specific availability.
119+
For more information, see [Create custom availability strategy](create_custom_availability_strategy.md).

docs/search/criteria_reference/productavailability_criterion.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ description: ProductAvailability Search Criterion
44

55
# ProductAvailability Criterion
66

7-
The `ProductAvailability` Search Criterion searches for products by their availability.
7+
The `ProductAvailability` Search Criterion searches for products by the availability flag, the boolean value set per product or variant.
8+
9+
To search for products that can be ordered, recreate the availability conditions with [existing product search criteria](product_search_criteria.md), for example [LogicalAnd](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-LogicalAnd.html), [LogicalOr](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-Product-Query-Criterion-LogicalOr.html), and [`ProductStock`](productstock_criterion.md).
10+
To recreate complex [custom availability strategies](create_custom_availability_strategy.md), you might need to implement [custom search criteria](search_criteria_and_sort_clauses.md#custom-criteria-and-sort-clauses) for the conditions not covered by the built-in ones.
11+
12+
For more information, see [Availability and computed availability](products.md#availability-and-computed-availability).
813

914
## Arguments
1015

@@ -43,4 +48,4 @@ $query = new ProductQuery(
4348
}
4449
}
4550
}
46-
```
51+
```

mkdocs.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,12 +386,13 @@ nav:
386386
- Prices: product_catalog/prices.md
387387
- Price API: product_catalog/price_api.md
388388
- Customize product catalog:
389-
- Customize product catalog: product_catalog/customize_product_catalog.md
390389
- Create custom attribute type: product_catalog/create_custom_attribute_type.md
391-
- Create product code generator: product_catalog/create_product_code_generator.md
390+
- Create custom availability strategy: product_catalog/create_custom_availability_strategy.md
392391
- Create custom catalog filter: product_catalog/create_custom_catalog_filter.md
393392
- Create custom name schema: product_catalog/create_custom_name_schema_strategy.md
393+
- Create product code generator: product_catalog/create_product_code_generator.md
394394
- Customize product attribute templates: product_catalog/customize_product_attribute_templates.md
395+
- Customize product catalog: product_catalog/customize_product_catalog.md
395396
- Customize product embed templates: product_catalog/customize_product_embed_templates.md
396397
- Add remote PIM support: product_catalog/add_remote_pim_support.md
397398
- Commerce:

0 commit comments

Comments
 (0)