Appearance
Meduse catalog structure changes
Use this guide when a catalog change crosses a screen, API, database record, and connector. First identify the object being changed. An attribute defines product data; a family groups attributes and variation axes; a category classifies products; a menu controls how categories appear for a channel, store, or device. A menu row and its linked catalog category have different IDs and different jobs.
For the route and component map, see Interfaces. The full route list is in API endpoints; Workflows, Queues, and Connectors cover adjacent processing paths.
View diagram source
flowchart LR
A[Attribute form] -->|POST save-attribute| AC[CatalogAttributeController]
AC --> AD[(Attribute, translations, options, family links)]
AC -->|central / Menzzo| AJ[Attribute export job]
AC -->|MeublePromo| APJ[Attribute export job]
AJ --> AF[FTP export_attributes/pending]
APJ --> AF
F[Family form] -->|POST save-family| FC[CatalogFamilyController]
FC --> FD[(Family, translations, attributes, variants)]
FC -->|central / Menzzo or MeublePromo| FJ[Family feed job]
FJ --> FF[FTP family/pending]
C[Category admin] -->|category endpoints| CC[Category controller / service]
CC --> CD[(Catalog category, menu row, product links)]
CC -->|new Mosaikasa category| CJ[Resolve or create and publish Shopify collection]
M[Menu builder] -->|POST menu/save-menu| MC[MenuController]
MC --> MD[(Channel/store/device menu rows)]
M -->|Synchroniser| BR{Tenant branch}
BR -->|central / Menzzo or MeublePromo| MJ[CSV and JSON export jobs]
MJ --> MF[FTP pending and menu_json files]
BR -->|Mosaikasa| SJ[Shopify menu job]
BR -->|MakeUp| MJ2[Inline Artisan command]
SJ --> S[Shopify menu]
MJ2 --> S
Change an attribute
Open Attributs, then add or edit an attribute. The detail form loads the selected attribute, attribute groups, types, locales, family links, and—when opened—the option list. It collects the group and backend type, uniqueness/scope/grid/front visibility flags, translations, family links, and option additions or removals.
On save, the frontend posts attribute_id and a data object containing general, translate_data, and options.add/options.remove to POST /api/save-attribute. The backend handler creates or updates catalog_attribute, then writes translation, option, and family-link rows. On create it stores a slug of the French label as the attribute code; on update it does not change that code. The form disables Save unless a group and French translation are set. The backend's create-time duplicate check compares existing codes with the raw French label before storing its slug, so check the generated code for collisions when the label needs slugification.
Review these replacement behaviors before saving:
- The backend deletes all current family links for the attribute, then inserts the selected family links. An empty selection clears them.
- Non-empty translations are inserted or updated. A blank translation is skipped, so clearing a field in the form does not remove the stored translation.
- Removed options have their localized values and option row hard-deleted. Editing an option label uses
POST /api/edit-option-label, which deletes and recreates all localized values for that option. - The Attributs list's remove action calls
POST /api/to-remove-attributeand setsto_delete = 1; it does not hard-delete the attribute. This method does not enqueue the catalog export.
After a successful save, central (no tenant) requests enqueue the Menzzo attribute export on the named export queue; the meuble_promo tenant enqueues its own export on that queue. Those jobs write an attribute file below /meduse/connectors/menzzo/export_attributes/pending/ or /meduse/connectors/meublepromo/export_attributes/pending/. The Menzzo job exits without exporting when its connector is missing or disabled. The controller's success response proves the Meduse save, not that a worker exported or a downstream system imported the file. The source does not show an attribute export branch for Mosaikasa or MakeUp.
Change a family
Open Familles, add or edit a family, then set its code, title attribute, image attribute, translations, selected attributes, and variation definitions. A variation contains a code, title, and one or two selected attribute axes. The family form sends its values through the family API module.
The form posts id, general, famillyTranslation, attributs, variations.toAdd/variations.toRemove, attrToAdd, and attrToRemove to POST /api/save-family. The backend handler requires French and English titles, at least one selected attribute, and a unique code on create. For newly added variations it checks code uniqueness and requires each axis to belong to the selected family attributes.
The handler writes catalog_family and replaces its translation rows. It replaces the family-attribute links when attributes are supplied, creates new variant/axis rows, and deletes removed variant rows, translations, and their family-to-attribute-set link. No transaction is visible around these writes. The UI also protects a fixed set of default attributes from removal.
Central/Menzzo and MeublePromo saves enqueue different family jobs on the named processing queue; their feed services write a CSV under /meduse/connectors/menzzo/family/pending/ or /meduse/connectors/meublepromo/family/pending/. The Menzzo job skips the feed when its connector is missing or disabled. This is a handoff; the Meduse response is not proof that Logidav or another consumer applied it. The controller's validation-error path also attempts to enqueue an export when it finds a family with the submitted code, so inspect the job/file before retrying after a family-save error. No Mosaikasa family export branch is shown here.
Changing a family's definition is separate from assigning products to it. The product-family action uses POST /api/save-product-families, updates each product's family_id, and then dispatches a product-family export for central/Menzzo or MeublePromo.
Change a category
The Administrer les catégories screen is linked from the menu builder for the mosaikasa tenant. Its tree is built from menu rows for channel ID 3; the tree key is the menu-row ID, not catalog_category.id.
Nouvelle catégorie sends categoryName, parentCategory, and isParentCategory to POST /api/add-new-category. In the checked Mosaikasa path, the category controller and sync job create the catalog category, a channel-3 menu row, and French/English translations, then resolve or create and publish the Shopify collection and save its Shopify ID on the catalog category. A success response means the request reached this path; verify the job and stored Shopify ID before treating the category as available in Shopify.
Modifier les catégories posts categoriesToUpdateName to POST /api/update-category-name. The service updates menu.name and the French catalog_category_translation row for each selected category. This method does not call Shopify directly; use the separate menu synchronization path to publish a changed menu title.
Changer de parent posts a categories array to POST /api/update-parent-category. The service changes both the catalog hierarchy and menu parent/level, recalculates child menu positions, and can move or add product-to-category links as products roll up to new parents. This changes product classification as well as navigation structure; compare affected product-category associations in development before accepting the result.
Masquer/Afficher calls POST /api/update-categories-to-use-parent-category or POST /api/update-categories-to-stop-using-parent-category. These endpoints toggle menu.use_parent_category; they do not delete the catalog category. The Shopify menu job excludes rows marked to use their parent, so this local flag change needs the separate Shopify menu synchronization to affect the published menu.
The create, rename, and reparent routes initialize the Mosaikasa tenant and use its category service path; hide/show writes the menu flag in the controller. Do not assume the same behavior applies to another tenant without tracing that tenant's route and service.
Change a menu
Open Gestion des catégories and select the channel, store, and—on central/Menzzo—device type. Select a parent category to edit its children, names, ordering, columns, visibility, images, or attributes shown in that category. The menu builder sends its normalized tree and changed rows to POST /api/menu/save-menu, including the selected categoryModal and deviceType where applicable.
MenuController::saveMenu updates menu rows: name, level, parent, position, column, image, hidden state, visible-attribute IDs, and supported menu metadata. It then deletes and recreates catalog_category_attribute_positions for each changed category. The controller has no explicit request validation or transaction in this method, so keep the tenant, channel/store, device, row IDs, and request body together when reviewing a save.
Category image uploads go first to a temporary public-storage path. When the menu is saved, the controller attempts to move a temporary image into the category image directory and stores the resulting path. The menu save rebuilds extra_data from the six Menzzo/Asir/DropShip fields and right_image; any other existing keys in that JSON object are not carried forward. It also rebuilds the category attribute-position rows, so an empty submitted attribute list clears those positions.
Saving and publishing are separate actions. Enregistrer persists Meduse menu data. Synchroniser selects a connector path from the tenant:
- With no tenant (central/Menzzo), the screen calls
GET /api/menu/export-menu-csv?deviceType=Desktop|Mobile. The controller builds a CSV and queues CSV and JSON jobs onexport. The CSV job uploads below/meduse/connectors/menzzo/menu/pending/; the JSON job writes per-channel files below/meduse/connectors/menzzo/menu/menu_json/<deviceType>/. - For
meuble_promo, the same endpoint queues the MeublePromo CSV and JSON jobs onexport. They upload to/meduse/connectors/meublepromo/menu/pending/and/meduse/connectors/meublepromo/menu/menu_json/. - For
mosaikasa, the screen callsGET /api/update-shopify-menu. The endpoint queuesUpdateShopifyMenuonexport; that job builds menu items from active categories with Shopify IDs, excludesuse_parent_categoryrows, submits the Shopify menu mutation, then updates collection position metafields. The endpoint response says the update was triggered; confirm the job/API outcome and the Shopify menu itself. - For
make_up, the screen callsGET /api/update-make-up-menu. The controller callsmakeup:update-menuinline and returns captured command output. The command submits the MakeUp menu to Shopify. The controller does not check the Artisan exit code, so verify the returned output and the Shopify menu.
A queue response, HTTP 200, or “synchronisation launched” toast is not an import acknowledgement. For file feeds, follow the job through the appropriate queue and verify the expected file plus the consumer's completion/acknowledgement. For Shopify, verify the final menu and collection links in the target development store.
Development validation checkpoints
Use an approved development API and matching tenant. Keep each check attached to the object and context that produced it:
- Record the tenant, channel/store, device type, screen action, object ID, and request payload. For categories, distinguish the
menu.idfrom itscategory_id. - Confirm the request/response at the API boundary and inspect the corresponding development rows: attribute plus translations/options/family links; family plus translations/attributes/variants; category plus translations/product links; or menu rows plus category-attribute positions.
- Check for destructive replacements described above before comparing the result. For category reparenting, include affected product-category links; for menu image changes, confirm the final stored image path.
- If work was queued, follow the named queue and job record, then check the file in its connector
pendingpath or the Shopify API result. A job record alone does not prove downstream consumption. - Close the journey at the consumer boundary: confirm the connector's completion or error outcome, or confirm the category/menu in the target development storefront. Production state and live connector configuration were not inspected for this guide.
Evidence boundary
This page describes source behavior in the backend commit and frontend snapshot listed above. It is code-level evidence; no application, database, Artisan command, FTP endpoint, Shopify store, production system, or live connector was exercised. Runtime queue configuration, live connector enablement, deployed revisions, and downstream acceptance still require development-environment evidence. See Queues and Workflows for the wider queue and connector boundaries.