diff --git a/code_samples/back_office/search/src/Query/SymbolAttributeTypeQuery.php b/code_samples/back_office/search/src/Query/SymbolAttributeTypeQuery.php new file mode 100644 index 0000000000..2c449d97b8 --- /dev/null +++ b/code_samples/back_office/search/src/Query/SymbolAttributeTypeQuery.php @@ -0,0 +1,9 @@ +setFilter(new SymbolAttribute('ean', ['5023920187205'])); +/** @var \Ibexa\Contracts\ProductCatalog\ProductServiceInterface $productService */ +$results = $productService->findProducts($query); diff --git a/code_samples/pim/Symbol/Format/Checksum/LuhnChecksum.php b/code_samples/pim/Symbol/Format/Checksum/LuhnChecksum.php new file mode 100644 index 0000000000..2860f9aec9 --- /dev/null +++ b/code_samples/pim/Symbol/Format/Checksum/LuhnChecksum.php @@ -0,0 +1,46 @@ +getDigits($value); + + $count = count($digits); + $total = 0; + for ($i = $count - 2; $i >= 0; $i -= 2) { + $digit = $digits[$i]; + if ($i % 2 === 0) { + $digit *= 2; + } + + $total += $digit > 9 ? $digit - 9 : $digit; + } + + $checksum = $digits[$count - 1]; + + return $total + $checksum === 0; + } + + /** + * Returns an array of digits from the given value (skipping any formatting characters). + * + * @return int[] + */ + private function getDigits(string $value): array + { + $chars = array_filter( + str_split($value), + static fn (string $char): bool => $char !== '-' + ); + + return array_map('intval', array_values($chars)); + } +} diff --git a/code_samples/symbol_attribute/config/mysql/symbol_attribute.sql b/code_samples/symbol_attribute/config/mysql/symbol_attribute.sql new file mode 100644 index 0000000000..5157c20e37 --- /dev/null +++ b/code_samples/symbol_attribute/config/mysql/symbol_attribute.sql @@ -0,0 +1,2 @@ +CREATE TABLE ibexa_product_specification_attribute_symbol (id INT NOT NULL, value VARCHAR(160) DEFAULT NULL, INDEX ibexa_product_specification_attribute_symbol_value_idx (value), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_520_ci` ENGINE = InnoDB; +ALTER TABLE ibexa_product_specification_attribute_symbol ADD CONSTRAINT ibexa_product_specification_attribute_symbol_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE \ No newline at end of file diff --git a/code_samples/symbol_attribute/config/postgresql/symbol_attribute.sql b/code_samples/symbol_attribute/config/postgresql/symbol_attribute.sql new file mode 100644 index 0000000000..4ad6b65bef --- /dev/null +++ b/code_samples/symbol_attribute/config/postgresql/symbol_attribute.sql @@ -0,0 +1,3 @@ +CREATE TABLE ibexa_product_specification_attribute_symbol (id INT NOT NULL, value VARCHAR(160) DEFAULT NULL, PRIMARY KEY(id)); +CREATE INDEX ibexa_product_specification_attribute_symbol_value_idx ON ibexa_product_specification_attribute_symbol (value); +ALTER TABLE ibexa_product_specification_attribute_symbol ADD CONSTRAINT ibexa_product_specification_attribute_symbol_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; \ No newline at end of file diff --git a/composer.json b/composer.json index eebf291e30..734baa69ba 100644 --- a/composer.json +++ b/composer.json @@ -70,7 +70,8 @@ "ibexa/twig-components": "~4.6.x-dev", "ibexa/tree-builder": "~4.6.x-dev", "ibexa/discounts": "~4.6.x-dev", - "ibexa/discounts-codes": "~4.6.x-dev" + "ibexa/discounts-codes": "~4.6.x-dev", + "ibexa/product-catalog-symbol-attribute": "~4.6.x-dev" }, "scripts": { "fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots", diff --git a/docs/ibexa_products/editions.md b/docs/ibexa_products/editions.md index 02b083b3ca..6accca22ad 100644 --- a/docs/ibexa_products/editions.md +++ b/docs/ibexa_products/editions.md @@ -65,3 +65,4 @@ The features brought by LTS Updates become standard parts of the next LTS releas | [AI Actions](ai_actions_guide.md) | ✔ | ✔ | ✔ | | [Date and time attribute type](date_and_time.md) | ✔ | ✔ | ✔ | | [Discounts](discounts.md) | | | ✔ | +| [Symbol attribute type](symbol_attribute_type.md) | ✔ | ✔ | ✔ | \ No newline at end of file diff --git a/docs/pim/attributes/symbol_attribute_type.md b/docs/pim/attributes/symbol_attribute_type.md new file mode 100644 index 0000000000..a72b04f5dd --- /dev/null +++ b/docs/pim/attributes/symbol_attribute_type.md @@ -0,0 +1,138 @@ +--- +description: Create a symbol attribute type that enables for the efficient representation of string-based values while enforcing their format in product specifications. +edition: lts-update +--- + +# Symbol attribute type + +The Symbol attribute type LTS update enables the efficient representation of string-based data and enforces their format in product specifications. + +This feature allows you to store standard product identifiers (such as EAN or ISBN) in the [Product Information Management](pim_guide.md) system. + +## Installation + +### Download the bundle + +To get the most recent stable version of this bundle, open a command terminal, navigate to your project directory, and run the following command: + +``` bash +composer require ibexa/product-catalog-symbol-attribute +``` + +### Enable the bundle + +Symfony Flex enables and configures the `IbexaProductCatalogSymbolAttributeBundle` automatically. +If you don't use it, you can manually enable this bundle by adding the line below to the Kernel of your project: + +``` php +// config/bundles.php + +return [ + // ... + Ibexa\Bundle\ProductCatalogSymbolAttribute\IbexaProductCatalogSymbolAttributeBundle::class => ['all' => true], + // ... +]; +``` + +### Update database schema + +To store symbol attribute values, the `IbexaProductCatalogSymbolAttributeBundle` needs an extra table. +The following SQL query can be used to build the required database structure: + +=== "MySQL" + + ``` sql + [[= include_file('code_samples/symbol_attribute/config/mysql/symbol_attribute.sql', glue=' ') =]] + ``` + +=== "PostgreSQL" + + ``` sql + [[= include_file('code_samples/symbol_attribute/config/postgresql/symbol_attribute.sql', glue=' ') =]] + ``` + +### Create symbol attribute definition (optional) + +Now, you're able to define symbol attributes at this point. + +To create symbol attribute definition, in the back office, go to **Product catalog** -> **Attributes**, and click **Create**. +Then, choose **Symbol** attribute type. + +## Build-in symbol attribute formats + +The built-in symbol attribute formats in `ibexa/product-catalog-symbol-attribute` are listed below: + +| Name | Description | Example | +|-----------------|-----------------|-----------------| +| Generic | Accepts any string value | #FR1.2 | +| Generic (alphabetic characters only) | Accepts any string value that contais only letters | ABCD | +| Generic (digits only) | Accepts any string value that contais only digits | 123456 | +| Generic (alphanumeric characters only) | Accepts any string value that contains only letters or digits | 2N6405G | +| Generic (hexadecimal digits only) | Accepts any string value that contains only hexadecimal digits (digits or A-F characters) | DEADBEEF | +| EAN-8 | European Article Number (8 characters) | 96385074 | +| EAN-13 | European Article Number (13 characters) | 5023920187205 | +| EAN-14 | European Article Number (14 characters) | 12345678901231 | +| ISBN-10 | International Standard Book Number (10 characters) | 0-19-852663-6 | +| ISBN-13 | International Standard Book Number (13 characters) | 978-1-86197-876-9 | + +!!! caution + + Maximum length of the symbol value is 160 characters. + +## Create custom symbol attribute format + +Under the `ibexa_product_catalog_symbol_attribute.formats` key, you can use configuration to create your own symbol format. + +See the example below: + +``` yaml +ibexa_product_catalog_symbol_attribute: + formats: + manufacturer_part_number: + name: 'Manufacturer Part Number' + pattern: '/^[A-Z]{3}-\d{5}$/' + examples: + - 'RPI-14645' + - 'MSS-24827' + - 'SEE-15444' +``` + +This following example specifies the format for a "Manufacturer Part Number", defined with the `manufacturer_part_number` identifier. + +The pattern is specified using a regular expression. +According to the pattern option, the attribute value: + +- must be a string +- begins with three capital letters (A-Z), followed by a hyphen ("-") +- ends with five digits (0-9), with no other characters before or after + +Certain formats, such as the International Standard Book Number (ISBN-10) and the European Article Number (EAN-13), contain checksum digits and are self-validating. + +To validate checksum of symbol: + +1\. Create a class implementing the `\Ibexa\Contracts\ProductCatalogSymbolAttribute\Value\ChecksumInterface` interface. + +2\. Register the class as a service using the `ibexa.product_catalog.attribute.symbol.checksum` tag and specify the format identifier using the `format` attribute. + +See below the example implementation of checksum validation using Luhn formula: + +``` php +[[= include_file('code_samples/pim/Symbol/Format/Checksum/LuhnChecksum.php') =]] +``` + +Example service definition: + +``` yaml +services: + App\PIM\Symbol\Format\Checksum\LuhnChecksum: + tags: + - name: ibexa.product_catalog.attribute.symbol.checksum + format: my_format +``` +The format attribute (`my_format`) is the identifier used under the `ibexa_product_catalog_symbol_attribute.formats` key. + +## Search for products with given symbol attribute + +You can use `SymbolAttribute` Search Criterion to find products by symbol attribute: + +For more information, see [SymbolAttribute Criterion](symbolattribute_criterion.md). \ No newline at end of file diff --git a/docs/search/criteria_reference/product_search_criteria.md b/docs/search/criteria_reference/product_search_criteria.md index a31f6cabbc..35a3948ac3 100644 --- a/docs/search/criteria_reference/product_search_criteria.md +++ b/docs/search/criteria_reference/product_search_criteria.md @@ -42,4 +42,4 @@ Search Criterion let you filter product by specific attributes, for example, col |[RangeMeasurementAttributeMinimum](rangemeasurementattributeminimum_criterion.md)|Minimum value of product's measurement attribute| |[SelectionAttribute](selectionattribute_criterion.md)|Value of product's selection attribute| |[SimpleMeasurementAttribute](simplemeasurementattribute_criterion.md)|Value of product's measurement attribute| - +|[SymbolAttribute](symbolattribute_criterion.md)|Value of product's symbol attribute| diff --git a/docs/search/criteria_reference/symbolattribute_criterion.md b/docs/search/criteria_reference/symbolattribute_criterion.md new file mode 100644 index 0000000000..53c6a241cd --- /dev/null +++ b/docs/search/criteria_reference/symbolattribute_criterion.md @@ -0,0 +1,21 @@ +--- +description: SymbolAttribute Criterion +edition: lts-update +--- + +# SymbolAttributeCriterion + +The `SymbolAttribute` Search Criterion searches for products by [symbol attribute](symbol_attribute_type.md). + +## Arguments + +- `identifier` - identifier of the format +- `value` - array with the values to search for + +## Example + +### PHP + +``` php +[[= include_file('code_samples/back_office/search/src/Query/SymbolAttributeTypeQuery.php') =]] +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index ae83fcbcf8..9e83b1f939 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -349,6 +349,7 @@ nav: - Products: pim/products.md - Attributes: - Date and Time attribute: pim/attributes/date_and_time.md + - Symbol attribute type: pim/attributes/symbol_attribute_type.md - Product API: pim/product_api.md - Catalogs: pim/catalogs.md - Catalog API: pim/catalog_api.md @@ -592,6 +593,7 @@ nav: - RangeMeasurementAttributeMaximum: search/criteria_reference/rangemeasurementattributemaximum_criterion.md - SimpleMeasurementAttribute: search/criteria_reference/simplemeasurementattribute_criterion.md - SelectionAttribute: search/criteria_reference/selectionattribute_criterion.md + - SymbolAttribute: search/criteria_reference/symbolattribute_criterion.md - Order Search Criteria: - Order Search Criteria: search/criteria_reference/order_search_criteria.md - CompanyName: search/criteria_reference/order_company_name_criterion.md