diff --git a/code_samples/trainings/commerce/pim/001_product_modeling/config/packages/ibexa_product_catalog.yaml b/code_samples/trainings/commerce/pim/001_product_modeling/config/packages/ibexa_product_catalog.yaml new file mode 100644 index 0000000000..4bfaa9cd6c --- /dev/null +++ b/code_samples/trainings/commerce/pim/001_product_modeling/config/packages/ibexa_product_catalog.yaml @@ -0,0 +1,8 @@ +ibexa_product_catalog: + engines: + default: + type: local + options: + root_location_remote_id: ibexa_product_catalog_root + product_type_group_identifier: 'product' + variant_code_generator_strategy: 'per_product_type_code_generator' diff --git a/code_samples/trainings/commerce/pim/001_product_modeling/config/services.yaml b/code_samples/trainings/commerce/pim/001_product_modeling/config/services.yaml new file mode 100644 index 0000000000..7ee033437f --- /dev/null +++ b/code_samples/trainings/commerce/pim/001_product_modeling/config/services.yaml @@ -0,0 +1,14 @@ +services: + # … + + App\CodeGenerator\Strategy\ProductTypeCodeGeneratorDispatcher: + arguments: + $defaultCodeGeneratorIdentifier: 'incremental' + $productTypeCodeGeneratorMap: + bike: 'bike' + tags: + - { name: 'ibexa.product_catalog.code_generator', type: 'per_product_type_code_generator' } + + App\CodeGenerator\Strategy\BikeCodeGenerator: + tags: + - { name: 'ibexa.product_catalog.code_generator', type: 'bike' } diff --git a/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/BikeCodeGenerator.php b/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/BikeCodeGenerator.php new file mode 100644 index 0000000000..5e84a1d29c --- /dev/null +++ b/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/BikeCodeGenerator.php @@ -0,0 +1,26 @@ +hasBaseProduct()) { + throw new InvalidArgumentException('$context', 'missing base product'); + } + + $frameSize = $context->getAttributes()['frame_size']; + $wheelSize = $context->getAttributes()['wheel_diameter']; + $frameShape = $context->getAttributes()['frame_shape'][0]; + $gearBundle = $context->getAttributes()['gear_bundle']; + + return $context->getBaseProduct()->getCode() . "--$frameSize-$wheelSize-$frameShape-$gearBundle"; + } +} diff --git a/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/ProductTypeCodeGeneratorDispatcher.php b/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/ProductTypeCodeGeneratorDispatcher.php new file mode 100644 index 0000000000..a918f00659 --- /dev/null +++ b/code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/ProductTypeCodeGeneratorDispatcher.php @@ -0,0 +1,42 @@ +codeGeneratorRegistry = $codeGeneratorRegistry; + $this->defaultCodeGeneratorIdentifier = $defaultCodeGeneratorIdentifier; + $this->productTypeCodeGeneratorMap = $productTypeCodeGeneratorMap; + } + + public function generateCode(CodeGeneratorContext $context): string + { + if (!$context->hasBaseProduct()) { + throw new InvalidArgumentException('$context', 'missing base product'); + } + + $productTypeIdentifier = $context->getBaseProduct()->getProductType()->getIdentifier(); + $codeGeneratorIdentifier = array_key_exists($productTypeIdentifier, $this->productTypeCodeGeneratorMap) ? $this->productTypeCodeGeneratorMap[$productTypeIdentifier] : $this->defaultCodeGeneratorIdentifier; + + if ($this->codeGeneratorRegistry->hasCodeGenerator($codeGeneratorIdentifier)) { + return $this->codeGeneratorRegistry->getCodeGenerator($codeGeneratorIdentifier)->generateCode($context); + } else { + throw new InvalidArgumentException('$productTypeCodeGeneratorMap', "no code generator '$codeGeneratorIdentifier' registered"); + } + } +} diff --git a/docs/trainings/commerce/pim/000_syllabus.md b/docs/trainings/commerce/pim/000_syllabus.md new file mode 100644 index 0000000000..7a524082e2 --- /dev/null +++ b/docs/trainings/commerce/pim/000_syllabus.md @@ -0,0 +1,51 @@ +--- +description: PIM training +page_type: training +--- + +# PIM (Product Information Management) + +## Syllabus + +In this training, you learn how to create complex products locally, and to organize them. + +| Section | Estimated | Description | +|:--------------------------------------------|----------:|:----------------------------------------------------------| +| [Product modeling](011_product_modeling.md) | X minutes | Learn about product types, products and product variants. | +| [Product shelving](012_product_shelving.md) | Y minutes | Organize your products with categories and catalogs. | +| [Product exchange](021_product_exchange.md) | Z minutes | Explore REST API, PHP API, and migrations. | + +## Requirements + +### Previous knowledge + +- [Content management](content_management.md) + - [Content types](content_types.md) and [content items](content_model.md#content-items) + - [Taxonomy](taxonomy.md) +- [Templating](templating.md) + +### Ibexa DXP edition + +[[= product_name_headless =]] is the minimal edition required for this training. + +- [[= product_name_headless =]] [[= latest_tag_4_6 =]] +- [[= product_name_exp =]] [[= latest_tag_4_6 =]] +- [[= product_name_com =]] [[= latest_tag_4_6 =]] + +TODO: Make sure that everything used is in Ibexa DXP Headless scope, so to speak, ibexa/product-catalog. It mustn't use features from ibexa/storefront. + +### Cluster elements + +This training can be run on the minimal stack. + +| Service | Required | Value | +|--------------:|:--------:|:-------------| +| Search engine | No | (Legacy) | +| Cache pool | No | (Filesystem) | +| HTTP cache | No | | + +### Starting state + +To follow this training, you must install code, configuration, and data on top of a fresh installation. + +TODO: Experience clean install, or previous bike ride design and content? diff --git a/docs/trainings/commerce/pim/011_product_modeling.md b/docs/trainings/commerce/pim/011_product_modeling.md new file mode 100644 index 0000000000..31591d4059 --- /dev/null +++ b/docs/trainings/commerce/pim/011_product_modeling.md @@ -0,0 +1,304 @@ +--- +description: "PIM training: Product modeling" +edition: experience +page_type: training +--- + +# Product modeling + +## Product types + +The product type base concept is close to the content type one. +Like a content type structures a family of content items, a product type structures products. + +See a first conceptualisation of what a product is, and what a product type is, in [Documentation > PIM (Product management) > Products](products.md). + +In fact, a product type is really a content type from the hidden system group `product` with a field of type “Product specification” (`ibexa_product_specification`). + +The presence of an `ibexa_product_specification` field is what distinct product type from content type. +Don't remove this field from a product type (or it becomes a unreachable hidden content type). +Don't add such field to a content type (or it becomes an uneditable broken product type). +You can't have more than one `ibexa_product_specification`, the laters won't be editable. + +You can trick the system URL to display a product type as a content type but know that this is dangerous and mustn't be exposed to final users. +Always prefer the dedicated route (as the back office does) `/product-type/view//contenttype/` (`ibexa.content_type.view`) is doable. You could even edit it from there. But this is strongly not recommended. + +The "Product specification" field type (`ibexa_product_specification`) brings in the power of attributes. +When defining the product model, the product schema, you go back and forth between attributes and product types. + +### Product type default fields + +When creating a new product type, several fields are already present. + +| identifier | type | +|:----------------------|:--------------------------------| +| name | ezsting | +| product_specification | ibexa_product_specification | +| description | ezrichtext | +| image | ezimageasset | +| category | ibexa_taxonomy_entry_assignment | + +Prices are not part of this training but: + +- Notice that you don't need to add a field or an attribute for price. Prices are handled by a particular side mechanism, the price engine, which is not treated in this training. +- Also notice that VAT is set at product type level. The associations of VAT categories to regions are also stored by the `ibexa_product_specification` field. + +Product assets presented bellow are explaining why there is no need to add fields to handle more images of the product. + +## Attributes VS Fields + +Like fields, attributes are typed. + +Unlike fields, attributes are not translatable in the product. +Attributes are translated from their definition. +Attributes are product constant properties. +Attributes values can't be translated from the product because those properties don't change with the language. +Only the display of those properties changes with the language. +See the following concept examples: + +- The color of a product is the same whatever the language is, only the corresponding color name is translated. +- The radius of a sphere doesn't depend on the language, only its numeral representation need translation according to local length units. + +Unlike fields, attributes are first defined outside the product types. +Attributes and attribute groups are to be reused from product type to product type. + +An attribute can be used to make product variant. + +## Product and product variants + +Technically, a product is a content item. + +But a variant isn't. +A variant has simpler representation +refering to the base product +and declaring values for the variable attributes. + +TODO: Continue content VS product VS variant + +A variant doesn't need translation. +It combines base product's and attributes' translations. + +## Product assets + +Product assets are collection(s) of images associated to a base product and eventually to its variants. + +The default `image` field is for an introduction image to the base product. +It's the image appearing in the product catalog's list of products. + +Assets are the images appearing on the product page. + +In the PIM, product assets are more powerful than a relation list or other field type could be. + +A collection of assets is associated to attribute values used for variants. +When displaying a product variant, the application combines the asset collections which suit its attribute values. +For example, you can associate close-up photos of a feature to the checkbox representing its presence, +while associating full views of the products to each of its available colors. + +Assets are not translatable. +If a product is not the same from a region to another, from a language to another, this isn't the same product. +For example, if you want to be able to select the language of the instruction manual whatever the user's region, +you could set this choice in a selection attribute, then load a preview asset to each of its values. + +## Exercise: Bike modeling + +Exercise: Think about attributes and content type to sell full bicycles and bicycle parts. + +The following is an example of a bicycle feature model sketch. +As you can see, it's a rich and complex matrix. +And it doesn't even involve different brands and models for the same feature yet! + +- Bicycle: + - Frameset: + - Frame: + - Size: [XS, S, M, L, XL] + - Shape/Features: [Diamond, Step-through, Diamond w/ suspension, Folding, Recumbent, Cargo, Tandem, …] + - Material: [Steel, Aluminum, Titanium, Carbon, Wood, Bamboo, Mixed, …] + - Paint job: […] + - Fork + - Size: [XS, S, M, L, XL] + - Suspension: yes/no + - Paint job: […] + - Handlebar: + - Shape: [Standard, Drop, Bullhorn, Flat, Riser, …] + - Paint job: […] + - Saddle: + - Shape: [Thin, Large, Noseless, …] + - Cushion material: [Foam, Gel, …] + - Cover material: [Spandex, Vinyl, Kevlar, Leather, …] + - Paint job: […] + - Saddlepost: + - Material: [Steel, Aluminum, Titanium, Carbon, Wood, Bamboo, Mixed, …] + - Type: [Rigid, Suspension, Dropper, …] + - Attachment: [Quick release, Bolt/nut, Anti-thief, …] + - Pedals: + - Type: [Flat, Quill, Clipless, …] + - Foldable: yes/no + - Gears: + - Front gears: + - Speed count: [1…3] + - Type: [Single, External, Hub internal, Crank gearbox, …] + - Control transmission: [Bowden cable, Hydraulic, Electronic, …] + - Control type: [Lever, Ring, …] + - Control placement: [Handlebar, Frame, …] + - Rear gears: + - Speed count: [1…12] + - Control type: [Bowden cable, Hydraulic, Electronic, …] + - Type: [Single, External, Hub internal, Crank gearbox, …] + - Control transmission: [Bowden cable, Hydraulic, Electronic, …] + - Control type: [Lever, Ring, …] + - Control placement: [Handlebar, Frame, …] + - Transmission: [Chain, Belt, Shaft, …] + - Wheel set: + - Rear wheel: + - Axle attachment: [Quick release, Bolt/nut, Thru, Anti-thief, …] + - Diameter: [622 mm (road 700C, mountain 29″), 584 mm (road 650B, mountain 27.5″), 559 mm (mountain 26″), 406 mm (mountain 20″), …] + - Type: [Standard spokes, G3 spokes, Disc, …] + - Brake: [Caliper, Disk, Roller, Drum, …] + - Rim material: [Aluminum, Steel, Carbon, …] + - Tire shape: [City, Race, Mountain, Fat, Mixed, …] + - Tire insert: [Clincher/Tube, Tubular, Tubeless, Foam, Solid, …] + - Paint job: […] + - Front wheel: + - Same as rear: ☑yes/☐no + - Axle attachment: [Same as rear, Quick release, Bolt/nut, Thru, Anti-thief, …] + - Diameter: [Same as rear, 622 mm (road 700C, mountain 29″), 584 mm (road 650B, mountain 27.5″), 559 mm (mountain 26″), 406 mm (mountain 20″), …] + - Type: [Same as rear, Standard spokes, G3 spokes, Disc, …] + - Brake: [Same as rear, Caliper, V, Disk, Roller, …] + - Rim material: [Same as rear, Aluminum, Steel, Carbon, …] + - Tire shape: [Same as rear, City, Race, Mountain, Fat, Mixed, …] + - Tire insert: [Same as rear, Clincher/Tube, Tubular, Tubeless, Foam, Solid, …] + - Electric assistance: + - Electric assistance: ☐yes/☑no + - Motor placement: [Front wheel, Rear wheel, Crank, …] + - Battery placement: [Center, Rear, …] + - Regulation: [EU pedelec, EU speed pedelec, …] + +A bad practice would be to try to have a unique product type for modeling all the bikes from the catalog. +For example, series of product won't necessarily vary on the same attributes. +TODO: To have one product type per base product can happen. + +The exercise consists into modeling the following catalog of mountain bikes (MTB). +The catalog is split in series, each series got few base products with variations. + +- Mountain Bike + - 4 Series + - Fuji + - Matterhorn + - Annapurna + - Etna + - 5 Series + - Kilimanjaro + - Stádda + - Aconcagua + - Ventoux + - Castor + +To simplify casual customer experience (and above all the exercise), the vendor don't give a lot of choices. + +- Manufacturers, brands and models are predefined. +- Front and rear gears, transmission and shifting system are set in one bundle with predefined models. +- Paint jobs are predefined palette/colorscheme/color set, and patterns. + +The following table shows only properties that can vary. When not all combinations are available, the product has multiple lines. + +| Series | Name | Base code | Material | Frame shape | Frameset + wheel size | Saddle | Paint job | Gears | Price | +|:---------|:------------|:----------|:---------|:---------------------------|:----------------------|:-----------------|:-----------------------|:--------:|------:| +| 4 Series | Fuji | MTB-S4-4 | Aluminum | Diamond | [S, M, L, XL] + 29″ | Thin | [Sakura, Ronin] | G02-2x10 | 3776€ | +| 4 Series | Fuji | MTB-S4-4 | Aluminum | [Diamond, Step-through] | [S, M, L] + 29″ | [Thin, Large] | [Sakura, Ronin] | G02-1x10 | 3676€ | +| 4 Series | Fuji | MTB-S4-4 | Aluminum | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | [Sakura, Ronin] | G02-1x08 | 3666€ | +| 4 Series | Matterhorn | MTB-S4-5 | Aluminum | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Snow, Rock] | G02-2x12 | 4478€ | +| 4 Series | Annapurna | MTB-S4-6 | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | Annapurna | G01-3x12 | 8091€ | +| 4 Series | Etna | MTB-S4-7 | Aluminum | [Diamond, Step-through] | [S, M, L, XL] + 29″ | [Thin, Large] | Etna | G02-1x06 | 3369€ | +| 4 Series | Etna | MTB-S4-7 | Aluminum | [Diamond, Step-through] | XS + 27.5″ | [Thin, Large] | Etna | G02-1x06 | 3339€ | +| 5 Series | Kilimanjaro | MTB-S5-0 | Aluminum | Step-through w/ suspension | [S, M, L, XL] + 29″ | [Thin, Large] | [Shira, Mawenzi, Kibo] | G03-2x12 | 5895€ | +| 5 Series | Stádda | MTB-S5-1 | Aluminum | Step-through | XS + [26″, 27.5″] | Large | [Sunrise, Sunset] | G04-1x03 | 1392€ | +| 5 Series | Aconcagua | MTB-S5-2 | Carbon | Diamond w/ suspension | [S, M, L, XL] + 29″ | [Thin, Noseless] | [Condor, Llama] | G01-3x12 | 6960€ | +| 5 Series | Ventoux | MTB-S5-3 | Aluminum | Step-through | XS + [26″, 27.5″] | [Thin, Large] | [Provence, Mistral] | G04-1x04 | 1910€ | +| 5 Series | Castor | MTB-S5-4 | Aluminum | Diamond | [S, M, L, XL] + 29″ | [Thin, Large] | [Castor, Pollux] | G03-2x12 | 4225€ | + +- Create the attribute group(s) +- Create the attributes (TODO: two ways in the BO, Attributes page, or attribute group "Attributes" tab) +- Create the product type(s) +- Create the products +- Create the product variants + +The product variants codes can be generated automatically from the base product code +by the default 'incremental' code generator as product variants codes won't be used in the training. +But, if you're curious, you can read or implements as a bonus the following custom code generators. + +??? note "Bonus: Code generator" + + A code generator is associated to a whole catalog/repository. + + See how to [create custom product code generator strategy](create_product_code_generator.md). + + To be able to generate product variants' codes from their attributes, a filtering/dispatching code generator is needed. + + In `config/services.yaml`, the code generator services declaration looks like this: + ```yaml + [[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/config/services.yaml', 0, None, ' ') =]] + ``` + The `ProductTypeCodeGeneratorDispatcher` is a code generator using other code generator depending on the product type. Its service receive a map associating product type itendifier to code generator type identifier. + It also have a default code generator for product type not in the map. + + ```php + [[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/ProductTypeCodeGeneratorDispatcher.php', 0, None, ' ') =]] + ``` + + The `BikeCodeGenerator` is a code generator dedicated to the `bike` product type. It must cover every `bike` attributes that can vary to ensure code unicity. + + ```php + [[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/src/CodeGenerator/Strategy/BikeCodeGenerator.php', 0, None, ' ') =]] + ``` + + In `config/packages/ibexa_product_catalog.yaml` is set the use of `per_product_type_code_generator` for the `default` engine/repository: + ```yaml hl_lines="8" + [[= include_file('code_samples/trainings/commerce/pim/001_product_modeling/config/packages/ibexa_product_catalog.yaml', 0, None, ' ') =]] + ``` + +??? note "TODO: Possible solution(s)" + + TODO: Propose grouped attributes and product type(s), illustrate their usage with few products and product variants. + TODO: Materials as 1 attribute. Paintjobs as 2 attributes, one per series to reduce selection list length? 3 attributes groups, one common, one per series? Then 2 product types, one per series? + +??? note "Bonus: Database schema" + + If you're curious, after having created product type(s), you can run the following SQL query to see the database representation: + + ```sql + SELECT cg.group_id, cg.group_name, g.is_system, c.id, c.identifier, c.version + FROM ezcontentclass AS c + JOIN ezcontentclass_classgroup AS cg ON c.id = cg.contentclass_id AND c.version = cg.contentclass_version + JOIN ezcontentclassgroup AS g ON cg.group_id = g.id + ORDER BY cg.group_id ASC, c.id ASC + ; + ``` + +??? note "Bonus: Translation" + + - Add a language to your installation (Admin > Languages > Add language). + - Translate your attribute groups and attributes using the "Translations" tab when you displaying a group or an attribute in the back office. + - Translate your product types. + - Translate your base products. + +Here are some assets for the Fuji bikes, and for the G02 bundles front gears: + +On Fuji, create the asset collections for the assets to be associated to the corresponding variants. +(Download the targets of the following links.) + +- [Fuji: Diamond frame + Ronin paint](Fuji-diamond-ronin.png) +- [Fuji: Diamond frame + Sakura paint](Fuji-diamond-sakura.png) +- [Fuji: Step-through frame + Ronin paint](Fuji-stepthrough-ronin.png) +- [Fuji: Step-through frame + Sakura paint](Fuji-stepthrough-sakura.png) +- [G02-2x10: Front gears](G02-2.png) +- [G02-1x10 and G02-1x8: Front gears](G02-1.png) + +For the Fuji base product generic image, use [this one](Fuji-diamond.png). + +??? note "TODO: Possible solution(s)" + + ![Fuji, example of asset collection for diamond frame shame with "Sakura" paint job.](Fuji-asset-collection-diamond-sakura.png) + +Your new products are in the "Uncategorized products" section of the **Products** admin page. +It's now time to fix this in the next chapter. diff --git a/docs/trainings/commerce/pim/012_product_shelving.md b/docs/trainings/commerce/pim/012_product_shelving.md new file mode 100644 index 0000000000..d82e53420a --- /dev/null +++ b/docs/trainings/commerce/pim/012_product_shelving.md @@ -0,0 +1,42 @@ +--- +description: "PIM training: Product shelving" +edition: experience +page_type: training +--- + +# Product shelving + +There is several ways to group and sort products. TODO + +## Catalogs + +[Catalogs](catalogs.md) group products by filtering on product data. + +TODO: What having several catalogs implies (navigation, customer experience,…) +TODO: How catalogs are displayed? Move this concern to "Product displaying" +TODO: [Create custom catalog](create_custom_catalog_filter.md) + +## Categories + +Product categories are tags in the `product_categories` [taxonomy](taxonomy.md). + +If you're curious, you can find the `product_categories` taxonomy configuration in `vendor/ibexa/product-catalog/src/bundle/Resources/config/prepend.yaml`. + +Exercise: + +- Create a "Bike" category, its child "Mountain Bike" category, with for the latter two children categories "4 Series" and "5 Series" +- Categorize the products from previous chapter's exercise into the right series: + +- Bike + - Mountain Bike + - 4 Series + - Fuji + - Matterhorn + - Annapurna + - Etna + - 5 Series + - Kilimanjaro + - Stádda + - Aconcagua + - Ventoux + - Castor diff --git a/docs/trainings/commerce/pim/021_product_exchange.md b/docs/trainings/commerce/pim/021_product_exchange.md new file mode 100644 index 0000000000..b25d202f15 --- /dev/null +++ b/docs/trainings/commerce/pim/021_product_exchange.md @@ -0,0 +1,210 @@ +--- +description: "PIM training: Product exchange" +edition: experience +page_type: training +--- + +# Product exchange + +## Remote PIM + +Even if this is outside the scope of this training, this is important to know that the products could be stored outside Ibexa DXP. +With a [remote PIM](pim_guide.md#remote-pim-support), Ibexa DXP role is to display them on the storefront, and, for the Commerce edition, to allow their purchase. + +## Product model migration + +After having modeled your catalog organization on your developer instance, +you may want/need to [generate migration files](exporting_data.md) for backup, or to install the model on a shared instance +(for example a staging instance, or the production instance where the creation of the final products is then done). + +Export attribute groups, attributes, and product types: + +``` bash +php bin/console ibexa:migrations:generate \ + --type=attribute_group --mode=create \ + --match-property=identifier --value=bike --value=mtb-s4 --value=mtb-s5 \ + --siteaccess=admin; + +# Where 2, 3, and 4 are the IDs of the attribute groups bike, mtb-s4, and mtb-s5 +php bin/console ibexa:migrations:generate \ + --type=attribute --mode=create \ + --match-property=attribute_group_id --value=2 --value=3 --value=4 \ + --siteaccess=admin; + +php bin/console ibexa:migrations:generate \ + --type=content_type --mode=create \ + --match-property=content_type_identifier --value=mtb-s4 --value=mtb-s5 \ + --siteaccess=admin; +``` + +Export product categories: + +``` bash +# Where 63 is the "Product Root Tag" Location ID (Product catalog > Categories) +php bin/console ibexa:migrations:generate \ + --type=content --mode=create \ + --match-property=parent_location_id --value=63 \ + --siteaccess=admin; +``` + +TODO: What about product migration limitation, and product variant migration inexistence? + +## REST API + +Thanks to REST API, [[= product_name =]]'s PIM can be shared with other applications. + +### Attribute groups and attributes + +| CRUD action | Method verb | Route | +|-------------|-------------|----------------------------------------| +| Create | POST | /product/catalog/attribute_groups | +| Read | GET | /product/catalog/attribute_groups/{id} | +| Update | PATCH | /product/catalog/attribute_groups/{id} | +| Delete | DELETE | /product/catalog/attribute_groups/{id} | + +TODO: GET /product/catalog/attribute_types +TODO: GET /product/catalog/attribute_types/{identifier} + +TODO: DELETE /product/catalog/attribute_groups/translation/{id}/{languageCode} + +| CRUD action | Method verb | Route | +|-------------|-------------|---------------------------------------------| +| Create | POST | /product/catalog/attributes | +| Read | POST | /product/catalog/attributes/view | +| Read | GET | /product/catalog/attributes/{id} | +| Update | PATCH | /product/catalog/attributes/{id}/{group_id} | +| Delete | DELETE | /product/catalog/attributes/{id} | + +TODO: DELETE /product/catalog/attributes/translation/{id}/{languageCode} + +### Product types + +| CRUD action | Method verb | Route | +|-------------|-------------|-----------------------------------------------------| +| Create | POST | /product/catalog/product_types | +| Read | GET | /product/catalog/product_types/{id} | +| Read | GET | /product/catalog/product_types/is_used/{identifier} | +| Read | POST | /product/catalog/product_types/view | +| Update | PATCH | /product/catalog/product_types/{id} | +| Delete | DELETE | /product/catalog/product_types/{id} | + +### Product and product variants + +| CRUD action | Method verb | Route | +|-------------|-------------|------------------------------------------------------| +| Create | POST | /product/catalog/products/{productTypeIdentifier} | +| Read | GET | /product/catalog/products/{code} | +| Read | POST | /product/catalog/products/view | +| Read | POST | /product/catalog/catalogs/{identifier}/products/view | +| Update | PATCH | /product/catalog/products/{code} | +| Delete | DELETE | /product/catalog/products/{identifier} | + +| CRUD action | Method verb | Route | +|-------------|-------------|--------------------------------------------------------------| +| Create | POST | /product/catalog/product_variants/{baseProductCode} | +| Create | POST | /product/catalog/product_variants/generate/{baseProductCode} | +| Read | GET | /product/catalog/product_variant/{code} | +| Read | POST | /product/catalog/product_variants/view/{baseProductCode} | +| Update | PATCH | /product/catalog/product_variants/{code} | +| Delete | DELETE | /product/catalog/product_variants/{code} | + +#### Product assets + +TODO + +### Catalogs + +| CRUD action | Method verb | Route | +|-------------|-------------|---------------------------------------------| +| Create | POST | /product/catalog/catalogs | +| Create | POST | /product/catalog/catalogs/copy/{identifier} | +| Read | GET | /product/catalog/catalogs/{identifier} | +| Read | POST | /product/catalog/catalogs/view | +| Update | PATCH | /product/catalog/catalogs/{identifier} | +| Delete | DELETE | /product/catalog/catalogs/{identifier} | + +### Categories + +TODO + +### TODO: Exercises + +TODO: Exercise request through REST the bikes from "5 series" +TODO: /product/catalog/catalogs/{identifier}/products/view + +## PHP API + +[[= product_name =]]'s PIM can be extended, accessed through custom controllers, command lines, or whatever you imagine, thanks to the PHP API. + +There are several services to read or modify the product model, the products, and other product related features. + +### Attribute groups and attributes + +[Product API > Attributes](product_api.md#attributes) gives example about attribute groups and attributes reading and writing. + +Notice that there are `AttributeGroupServiceInterface` and `AttributeDefinitionServiceInterface` for reading, +and `LocalAttributeGroupServiceInterface` and `LocalAttributeDefinitionServiceInterface` for writing. +This is due to reading being available for both local and remote PIM, while writing is available only locally. +This `Local` prefix in names of services allowing to write is recurrent. + +### Product types + +[Product API > Product types](product_api.md#product-types) illustrates how to access product types through the `ProductTypeServiceInterface`. + +To create product types, you could use +\Ibexa\ProductCatalog\Local\Repository\ProductType\ContentTypeFactoryInterface::createContentTypeCreateStruct +(`vendor/ibexa/product-catalog/src/lib/Local/Repository/ProductType/ContentTypeFactoryInterface.php`). +Notice that this interface isn't in the `Ibexa\Contracts` namespace, which means that its behavior could change in a future minor version. +As you can see in its `ContentTypeFactory::createContentTypeCreateStruct` implementation, +this function is responsible for the default fields of a new product type. + +### Product and product variants + +[Product API > Products](product_api.md#products) gets examples of products and product variants reading and writing. + +Notice again that there is a `ProductServiceInterface` for reading and a `LocalProductServiceInterface` for writing, +to distinguish what is available on both local and remote PIM, and what is available only locally. + +About product search with `ProductServiceInterface::findProducts()`, also see the following references: + +- [Criteria](product_search_criteria.md) +- [Sort Clauses](product_sort_clauses.md) + +#### Product assets + +[Product API > Products > Product assets](product_api.md#product-assets) has an example of asset reading. + +[`AssetServiceInterface`](../../../api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-AssetServiceInterface.html) is used to read product assets, +and [`LocalAssetServiceInterface`](../../../api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Local-LocalAssetServiceInterface.html) to write some. + +An asset collection as seen in the back office is represented by an associative array of tags (with attribute identifiers as keys), +and related getters and setters are named `getTags()` and `setTags` (for example, `AssetInterface::getTags()` or `AssetUpdateStruct::setTags()`). + +Write a command that display the assets of the 4 Series Fuji. + +```php + $fuji = $this->productService->getProduct('MTB-S4-4'); + $assets = $this->assetService->findAssets($fuji); + dump($assets); +``` + +Notice that `AssetServiceInterface::findAssets()` returns an `AssetCollectionInterface`. +`AssetCollectionInterface` isn't an asset collection as in the back office. It's just a traversable storing a list of assets without any hierarchy. +You can take a look at +\Ibexa\Bundle\ProductCatalog\UI\AssetGroup\AssetGroupCollectionFactory::createFromAssetCollection() +(`vendor/ibexa/product-catalog/src/bundle/UI/AssetGroup/AssetGroupCollectionFactory.php`). +to see how the back office transform the flat asset list into an "Assets collection for variant" list. + +### Catalogs + +[Catalog API](catalog_api.md) + +### Categories + +[Taxonomy API for product categories](taxonomy_api.md) + +### TODO: Exercises + +TODO: Exercise: Write a command/controller listing all mountain bikes by series. + +TODO: Exercise: "4 Series" has been discontinued. Create a "Retired product" category below root, move… diff --git a/docs/trainings/commerce/pim/img/Fuji-asset-collection-diamond-sakura.png b/docs/trainings/commerce/pim/img/Fuji-asset-collection-diamond-sakura.png new file mode 100644 index 0000000000..fb6e93dc1a Binary files /dev/null and b/docs/trainings/commerce/pim/img/Fuji-asset-collection-diamond-sakura.png differ diff --git a/docs/trainings/commerce/pim/resources/Fuji-diamond-ronin.png b/docs/trainings/commerce/pim/resources/Fuji-diamond-ronin.png new file mode 100644 index 0000000000..beb2d376bb Binary files /dev/null and b/docs/trainings/commerce/pim/resources/Fuji-diamond-ronin.png differ diff --git a/docs/trainings/commerce/pim/resources/Fuji-diamond-sakura.png b/docs/trainings/commerce/pim/resources/Fuji-diamond-sakura.png new file mode 100644 index 0000000000..83a746da50 Binary files /dev/null and b/docs/trainings/commerce/pim/resources/Fuji-diamond-sakura.png differ diff --git a/docs/trainings/commerce/pim/resources/Fuji-diamond.png b/docs/trainings/commerce/pim/resources/Fuji-diamond.png new file mode 100644 index 0000000000..918d5c706e Binary files /dev/null and b/docs/trainings/commerce/pim/resources/Fuji-diamond.png differ diff --git a/docs/trainings/commerce/pim/resources/Fuji-stepthrough-ronin.png b/docs/trainings/commerce/pim/resources/Fuji-stepthrough-ronin.png new file mode 100644 index 0000000000..71c9ddb9b8 Binary files /dev/null and b/docs/trainings/commerce/pim/resources/Fuji-stepthrough-ronin.png differ diff --git a/docs/trainings/commerce/pim/resources/Fuji-stepthrough-sakura.png b/docs/trainings/commerce/pim/resources/Fuji-stepthrough-sakura.png new file mode 100644 index 0000000000..06092c9457 Binary files /dev/null and b/docs/trainings/commerce/pim/resources/Fuji-stepthrough-sakura.png differ diff --git a/docs/trainings/commerce/pim/resources/G02-1.png b/docs/trainings/commerce/pim/resources/G02-1.png new file mode 100644 index 0000000000..50e9e88e5e Binary files /dev/null and b/docs/trainings/commerce/pim/resources/G02-1.png differ diff --git a/docs/trainings/commerce/pim/resources/G02-2.png b/docs/trainings/commerce/pim/resources/G02-2.png new file mode 100644 index 0000000000..dc312cfe89 Binary files /dev/null and b/docs/trainings/commerce/pim/resources/G02-2.png differ