Skip to content

Product Content Workflows ​

For a step-by-step creation task, use Product creation and publication. It distinguishes the two Publier actions and follows publication flags, files and returned Magento identifiers. The cross-system journey continues through Meduse and Magento.

This guide follows the active product-content leaves in src/AppBundle/Menu/Builder.php through their Symfony 3 annotations, Twig screens, page JavaScript, and persistence or integration boundary. The source is the immutable axelites/logidav snapshot at 88d9180215a5d489407498fe4905fdd38efd756d. This is a static map; it does not establish the deployed revision, live menu permissions, active cron/consumer state, Magento or Meduse acceptance, or current operator access.

Menu registration and route authorization are separate facts. The menu entries below are present in Builder.php; entry actions that call mz.menu_provider->hasAccess() are identified. Many AJAX handlers do not make that call themselves, and this page is not a complete security.yml or firewall audit. No Logidav runtime or browser testing was performed.

Source entry points ​

All links are pinned to 88d9180215a5d489407498fe4905fdd38efd756d.

The category and product-image screens update different records and have different downstream behavior:

View diagram source
flowchart LR
  Media[MediaLibraryBase + MediaResource] --> ProductMedia[Product media association]
  Deco[Product deco/cropped photo fields] --> DecoCommand[Async decor-image command]
  Universe[Product universe IDs] --> UniverseQueue[Pending ProductDataUpdateProcessor]
  NewCats[mz_new_category] --> NewCatMap[product_new_category mapping]
  CatalogCats[Product categories] --> MagentoCats[ProductApi category_links]
  Menu[MZ_MENZZO_CATEGORIES store config] --> MenuSync[Async categories sync command]

Diagram

Media library ​

Produits → Gestion des produits → Médiathèques opens a gallery; its Liste des produits child is a separate product table. The gallery can show every media record or, when productId is in the query string, only media linked to that product. MediaResource stores the model-to-media link separately from the media row and file.

UI actionMethod and routeInputs and result
Open galleryGET /medialibrary/ (medialibrary_index)Checks stock_stock_management_medialibrary_index, then renders the gallery. GET /medialibrary/display-all-media?productId=… loads linked files; without a product ID the repository returns all media rows.
Open the product tableGET /medialibrary/product-list (medialibrary_product_list)Checks its child menu key and renders MediaLibrary/products.html.twig. That template contains the standard product table; no link from it to the gallery route is visible in this snapshot.
Upload mediaGET /medialibrary/upload?productId=…, then POST /medialibrary/upload-media (upload_media)Dropzone sends files[], file_path=products, and optional product_id. The controller stores the file under data/media, inserts MediaLibraryBase, and, when a valid product ID is supplied, inserts a MediaResource link. The browser limits files to image/video and 10 MB; no equivalent server-side content/size validation is visible in this action.
Replace one media fileGET /medialibrary/edit/{id}, then POST /medialibrary/update-media/{id} (update_media)Shows linked products and accepts one uploaded file. The controller changes the media row’s filename/type and writes the replacement under data/media; a video is written as chunks here.
Assign selected mediaPOST /medialibrary/affect (affect_media_resource)Sends media_ids, modelIds, and model class. For each selected media it removes links to models outside the submitted set, then adds missing links. Reassigning a media item therefore changes its existing product links.
Delete selected mediaDELETE /medialibrary/delete-media (delete_media)Sends ids; removes each media row and its media-resource links, unlinks the physical file when present, and returns deleted/missing IDs. The UI asks for confirmation first.

Troubleshoot from the returned media_ids and the model links: upload without product_id creates an unlinked gallery record; assignment is a separate step. A productId filter only displays linked media. The edit page and its upload control use the same media-link action, so confirm the selected model IDs before saving an assignment.

Attribute sets shown on product forms ​

The active Gestion des attributes leaf opens GET /product/attribute/attributes-management (management_attributes_product). The entry action checks management_attributes_product; it renders a list of attribute sets, not a product-value editor. The “Modifier” action fetches the selected set’s configured attributes and the remaining attributes in its groups.

UI actionMethod and routeInputs and persistence
List/filter attribute setsGET or POST /product/attribute/attributesset-management-paginate (management_attributesset_product_paginate)DataTables columns, ordering, and paging go to AttributeSetService::searchProductAttributesSet().
Open a set’s attribute listsPOST /product/attribute/attributesset-attributes-list (attributesset_attributes_list)Sends attributeId; returns attributes already mapped to the set and the remaining grouped attributes.
Add/remove selected attributesPOST /product/attribute/manage-attributesset-attributes-list (manage_attributesset_attributes_list)Sends set id, selected attribute list, and action. Add persists AttributeSetsSpecificAttribute rows; any action other than add follows the remove branch. The UI requires a selection and asks for confirmation.

This changes which attribute definitions appear for an attribute set. It does not create attribute definitions, modify a product’s attribute values, or push product data to Magento. The two list/update handlers have no method restriction or hasAccess() call in their annotations/controller bodies; effective request authorization is not established by this source map.

Product universes ​

GET /product/univers-products-index (univers_products_index) checks univers_product, loads the product-universe attribute and category tree, and renders the editable table. The list route defaults to products without a universe; the page toggle changes the filter. Row edit and bulk selection open the same universe checkbox modal.

UI actionMethod and routeInputs and result
Search productsPOST /product/paginate-univers-products-index (paginate_univers_products_index)Sends table filters and intDisplay; rows show current universe labels and product category names.
Save one or many productsPOST /product/add-product-universes (product_add_product_universes)Sends productIds[] and selected universIds[]. For each Menzzo product, the controller stores the comma-separated universe IDs and adds a Pending ProductDataUpdateProcessor queue item for store 0 at at_5th_minute, inside a per-product transaction. The controller does not save non-Menzzo products in this branch.

The success response means the local handler completed; it does not prove the queued update ran or that Magento accepted it. The UI blocks an empty product selection, but the handler has no visible request validator for IDs or an empty universe selection.

New-category labels ​

GET /product/manage-new-categories-products (products_new_categories) checks stock_products_new_categories, loads mz_new_category, and lists products with their product_new_category mappings. These “new categories” are a Logidav mapping table; they are separate from the active Magento category tree used in Gérer les catégories.

UI action / source boundaryMethod and routeInputs and persistence
Search product rowsPOST /product/paginate-manage-new-categories-products (paginate_products_new_categories)Sends table filters and intDisplay; the default is nonUniverse, and the controller displays mapped new-category names.
Add category namesPOST /product/add-new-categories (product_add_new_categories)Sends option_values[]; exact-name duplicates are skipped and new names are inserted into mz_new_category. No name validation is visible in the handler.
Replace a product’s labelsPOST /product/add-new-categorie-to-product (product_add_new_categorie_to_product)Sends productIds[] and category IDs in universIds[]; deletes existing mapping rows for each product, then inserts the submitted mappings. Per-row insert exceptions are swallowed and the handler still returns success.

There is an important UI boundary in this snapshot: the Gestion des nouvelles catégories and bulk assignment triggers are commented out in the Twig page, and the per-row edit button is returned with the hidden class. The table and endpoints exist, but the normal page does not expose the category-manager or assignment actions. The default filter toggle is also commented out. Treat the POST handlers as source-visible capabilities, not proof that an operator can reach them through this screen.

Decoration images ​

GET or POST /product/product-deco-image (product_deco_image) checks product_deco_image and renders the decoration-image table. Voir tous les produits flips the allDeco filter. The “Mettre à jour sur le site” button is shown only to ROLE_ADMIN or users with CAN_SEND_PRODUCT_DECO_ROLLOVER_IMAGE in Twig.

UI actionMethod and routeInputs and state boundary
Load products and imagesPOST /product/paginate-product-deco-image-index (paginate_product_deco_image)Table filters plus allDeco; rows contain the current decoration and cropped-image URLs.
Choose another imagePOST /product/get-product-images-for-deco (get_product_images_for_deco)Sends product ID; controller asks ProductApi::getMediaProduct(sku, 3) and renders returned media URLs using the configured CDN base. A local image can also be selected in the browser; the UI accepts GIF/PNG/JPG/JPEG extensions.
Save selected decoration imagePOST /product/set-image-deco-product (set_image_deco_product)Sends id and src. A URL is saved on Product.decoPhoto; a browser data URL is copied to the product’s local decoration-image path and saved.
Open/crop current imagePOST /product/insert-new-image-deco, then POST /product/set-image-cropper-product (insert_new_image_deco, set_image_cropper_product)The first route writes a temporary source image under the product image path. The crop route writes the crop locally, sets croppedPhoto, decoPhoto, and isCropUpdated, saves the product, and synchronizes the associated NewProduct image through NewProductService when present.
Send decoration updatesPOST /product/update-image-deco-in-magento (update_image_deco_in_magento)A token starts menzzo:product:update-decor-image through mz.console; later requests poll the token log until !END!. The UI polls every five seconds and displays ERROR lines.

Choosing/cropping updates Logidav product image fields. “Mettre à jour sur le site” is a separate asynchronous command boundary. The role/capability test is visible in Twig, but the command endpoint itself has no corresponding hasAccess() call in this controller action; full route authorization was not audited. A completed polling marker is not live proof of site/provider acceptance.

Magento catalog categories ​

GET or POST /product/update_product_categories (update_product_categories) checks product_categories, builds the active category tree, and renders a product table. Each row’s edit button opens the tree with currently linked active categories checked; the page saves one product at a time.

UI actionMethod and routeInputs and state transitions
Search productsPOST /product/paginate-update_product_categories (paginate_update_product_categories)Sends table filters; rows return SKU, image, active category names, and edit action.
Save category selectionPOST /product/product-add-update-category (product_add_update_category)Sends productIds[] and categoryId[]. For each product, the handler fetches Magento by SKU to preserve existing positions, clears Magento category_links, removes local product-category links, adds the selected local links, then calls ProductApi::updateProductData() with the replacement links. Existing positions are preserved; new links default to position 0.

This handler catches exceptions and returns {success: "error", message: …}. Local writes and the two Magento calls are sequential and have no surrounding transaction in this method, so an error can leave the local and remote category sets at different stages. Check the product’s local links and Magento response/error before retrying. The page’s hasAccess() check does not by itself establish authorization on the POST endpoints.

Store category menu positions and sync ​

GET or POST /product/manage-sync-categories (manage_sync_categories) checks manage_sync_categories. The page reads the Menzzo menu configuration and lets the operator select a store and principal category. AJAX editing updates the MZ_MENZZO_CATEGORIES_<storeId> config record. These edits are local config changes until the separate sync action.

UI actionMethod and routeInputs, validation, and result
Load one principal categoryPOST /product/get-category-on-menu (get_category_on_menu)Sends idCategory and storeId; returns three rendered columns and parent configuration. It reports a missing store menu or category as a JSON error.
Add/remove child categoryPOST /product/add-sub-category-menu / POST /product/remove-sub-category-menu (add_sub_category_menu, remove_sub_category_menu)Sends principal/child IDs, column, parent as applicable, and store ID. Add rejects a duplicate in the selected menu column; successful changes save the config JSON.
Reorder menu or edit category configUI posts arrayJson, then category name/URL and Menzzo/Asir/DropShip shares/positions to POST /product/update-category-menu and POST /product/update-config-category (update_category_menu, update_config_category)Reorder serializes the three nestable columns and saves the principal tree. The config action calls CategoryService::checkFormConfigCategory(): percentages must sum to 100 and positions must sum to 6. Validation or update errors return a JSON message.
Change category imagePOST /product/update-image-cat-menu (update_image_cat_menu)Sends the selected image as a data URL with category, column, principal category, and store IDs; CategoryService updates the saved menu config.
Publish menu configPOST /product/sync-menu-magento (sync_menu_magento)Requires a token and store ID. First call runs menzzo:categories:sync --action=sync; subsequent calls read the token log and delete it at !END!. Twig polls every four seconds and displays command output.

The position editor and the sync are separate operations: a saved config is not evidence that the command ran or Magento’s menu changed. Only the page action visibly calls hasAccess(); the AJAX routes shown here do not repeat that check in their controller bodies.

EAN and parcel EANs ​

The two SKU forms look similar but their controller behavior differs. The menu exposes both Gérer les EAN produits and Générer un EAN.

Menu actionMethod and routeInputs and result
Manage product EANGET or POST /product/ean (manage_ean_product)Checks manage_ean_product; reads valSku. If the product already has an EAN, it displays it. If the product has no EAN, or no product row exists, it calls ProductService::generateNewEanForProduct(). That method reserves an EAN in mz_ean_product after checking reservation and product EAN conflicts; this handler does not set Product.ean. On generation failure it looks up the SKU’s reservation to display an existing EAN.
Generate an EANGET or POST /generate-ean (generate_ean)Checks generate_ean; reads valSku and calls the same reservation service. The source condition intended to skip products with an EAN contains && false, so this route still attempts generation for that case; a conflict/failure falls back to an existing reservation or a “cannot generate” message.
List parcel EANsGET /product/list-ean-by-parcel and GET/POST /product/paginate-ean-product-colis (list_ean_by_parcel, paginate_ean_product_colis)The page checks list_ean_by_parcel. The table/export handler reads filter/order/paging inputs through EanColisService; rows show SKU, image, name, product EAN, and parcel EAN-14 values. This is a read/export workflow.

If a generated code is shown but is not present on the product, check the mz_ean_product reservation: generation reserves a code there rather than assigning the product field. The menu page check is explicit for the three page actions; the paginator has no matching hasAccess() call in its body.

Translation and supplier content stages ​

The Traductions submenu has three active routes: Menzzo products, DropShip products, and Asir Group titles. Their entry actions all immediately exit() with the text “Veuillez traduire dans meduse” before the controller’s hasAccess() call or Twig render. The source therefore documents a placeholder message, not a working Meduse hand-off or an accessible translation table. Related data/update routes remain declared in TransController, but the menu entry does not reach them in this snapshot.

Supplier translation/publication is a separate workflow and has distinct Notio and Asir Group routes. Both entry handlers forward into the supplier’s creation page with step_3; they pass these states: ready_for_content_translation, to_publish, published, blocked, and content_translation_error. The shared table posts its supplier, statuses, and step to POST /new-product/paginate-dropship-new-products; “Voir” opens /new-product/add-update/{id}. The description-validation branch is step_2 and uses generation states (ready_for_content_generation, content_generation_ok, content_generation_error, description_contains_eng_sentences).

Supplier actionMethod and routeState boundary
Open translation/publication queueGET or POST /notio/translation-to-be-processed (translation_and_publication_process_index) or /asir-group/translation-to-be-processed (translation_and_publication_process_asir_group_index)Forwards to /notio/creation or /asir-group/creation, passing the provider-specific status list and step_3; both render NewProductDropship.html.twig with provider-specific table JavaScript.
Translate text in the editorPOST /new-product/translate (new_product_translate)Sends store ID and text; the controller calls its injected translation service and returns translated strings, or an error response. Saving remains a separate editor action.
Save edited supplier productGET or POST /new-product/save/{id} (new_product_save)Validates EANs, calls NewProductService::saveNewProduct(), writes history, and returns the updated editor URL.
Retry failed translationPOST /new-product/restart-the-translation (restart_the_translation)Selected IDs at step 3 are reset to ready_for_content_translation; the handler clears has_translation_error in a DB transaction. The UI refreshes the status table.
Prepare a product for publicationGET or POST /new-product/new-product-to-synchronise-in-magento (new_product_to_synchronise_in_magento)Checks product readiness, images, and EAN conflicts; then sets toPublish=1, records publisher/date on first publication, and initializes related product/warehouse state. It returns a preparation message; it does not itself prove publication.

The source also declares menzzo:new-product:synchronise --action=sync: its repository query selects NewProduct.toPublish = 1, and the command constructs a Magento-shaped payload. The RabbitMqService::publishMessage() call for that payload is commented out; this command instead sends the payload through MeduseService::sendNewProductToFtp(). FTP exceptions are caught and logged, but execution still changes toPublish to 2, so that value is not proof of successful transfer. Separate created-product consumer callbacks can persist returned Magento IDs/URLs and invoke menzzo:v2:products --productId to refresh product details; that callback is not the commented publish call and does not prove the batch payload reached Magento. Neither the UI preparation action nor these local state changes establish provider acceptance. The command schedule and live outcome are outside this static source snapshot.

What this source map does not prove ​

  • A menu item being built does not prove the logged-in user can see it or that every associated AJAX route is authorized. Only entry-action hasAccess() calls are listed; effective Symfony access control and live role assignments were not evaluated.
  • The source snapshot does not establish which revision is deployed, whether queues/cron/CLI workers are running, or whether Magento, Meduse, or the CDN accepted an update.
  • The generic Trans menu pages stop at the explicit Meduse placeholder exit. No Meduse destination URL, account context, or external translation UI is established here.
  • The new-category table’s add/assign handlers are present, but their normal triggering controls are commented out or hidden in this page.
  • No production, database, external-service, browser, or application execution was performed.

For stock quantities, purchasing, and product stock mutations, see Stock and Purchasing.

Internal developer documentation for Menzzo systems.