Skip to content

Pricing and Product Monitoring ​

This page follows the pricing, price-sheet, stock-valuation, and product-monitoring entries in src/AppBundle/Menu/Builder.php into their Symfony 3 controllers, Twig/JavaScript clients, services, repositories, local records, and asynchronous boundaries. The evidence is the pinned axelites/logidav source snapshot at 88d9180215a5d489407498fe4905fdd38efd756d. It describes source behavior only; it does not establish deployed permissions, current records, scheduler cadence, queue completion, or external-provider responses.

Pricing workflows ​

Menu labelRoute and purpose
Proposition d'offre tarifaireGET /product/product-price-proposal (product_price_tariff_propose_index) lists active visible products for review.
Base de calculGET /product/product-price-sheet/ (product_price_sheet) shows recommendation tiers, current store-3 price, and simulated price.
Log de calcul des prixGET /product/product-price-sheet/price-sheet-log (product_price_sheet_log) shows recalculation snapshots.
Calcul du prix des produitsGET, POST /new-product/calcul-product-price-index (calcul_product_price_index) calculates a read-only Menzzo/Drop/Depot price preview for one selected product.

Proposition d'offre tarifaire ​

The page checks product_price_tariff_propose on entry. Its DataTables request goes to GET|POST /product/paginate-product-price-tariff-propose-index (paginate_product_price_tariff_propose_index), where column search, sort, and paging flow to ProductRepository::paginateProductPricePriceProposalAction. The repository selects active products with visibility 2 or 4 and reads store 3 product information. The table returns SKU, weight, store-3 price, up to two photos, sellingPriceHt, and shipping amount divided by 1.2. This is a review/export list; its row flow does not edit the listed prices.

“Exporter tous” posts the selected columns, filters, and a token to /product/export-product-tariff-price (export_product_tariff_price). The controller starts Symfony command product:export:tariff-price-data once per token, then the browser polls the token block for completion and a generated XLS file. The command repeats the product query and writes under web/product_export_tariff_price/. It uses a lock; a missing/expired token, another active command, or an exception may leave the user without a completed file. The successful response from the start request is not evidence that export finished.

The page action has an explicit menu access check. The pagination and export actions shown in this source do not repeat that check; whether another security layer protects them is outside this source trace.

Base de calcul and price logs ​

The page checks product-price-sheet, then loads MZ_PRODUCT_LAST_FILL_BUYINGPRICE_AT, MZ_PROMO_PERCENTAGE, manufacturer currency/base records, and a date 30 days ahead for display. The AJAX list uses /product/product-price-sheet/paginate-price_sheet (asir_group_mode_product_price_sheet) with DataTables filters/order and optional export arguments. Each row combines a ProductPriceSheet record with the product's store-3 ProductInfo: buy and simulated prices, promo/current price, currency and volume, current price tier, and proposed average-sale-price tiers for 0–3, 3–6, 6–9, 9–12, and 12+ months. The controller applies the configured promo percentage to the displayed tier values.

There are three different edit actions in this table:

  • Editing the visible 6–9 month tier posts id, number, colum, and oldValue to /product/product-price-sheet/update-sheet-data-price (update_sheet_data_price). The controller accepts only colum=sell_price69; other columns return success:false. ProductPriceSheetService stores the entered value and derives the other tiers using 1.1, 1.05, 0.95, and 0.8667 multipliers. The controller compares the before/after simulated effective price, records grouped and detailed change-log rows, and sets last_manual_update. The input is labelled like a buying-price edit in the markup, but the stored field is the 6–9 month selling-price tier.
  • Editing “Prix simulé” posts id, value, and oldValue to /product/product-price-sheet/update-simulated-price (update_simulated_price). It normalizes comma decimals and rejects an empty value, then updates mz_product_price_sheet.simulated_price and appends a user/time-stamped ProductPriceSheetSimulatedPriceLog. This does not itself update the current store price.
  • “Reset” posts a product id to /product/product-price-sheet/reset-product-price-sheet (product_reset_product_price_sheet_price). The service recalculates from availability, 90-day sales, real quantity, incoming container quantities, product age, and composed-product rules, selects a tier, writes a ProductPriceSheetLog snapshot and updates the active price tag and store price records for the configured EU stores plus Boutique. The response is false for an empty/missing product; no calculated price also prevents a reset. Exceptions are not wrapped in a controller-level JSON error handler on this action.

The separate edit button for the actual sale price opens a modal with a selected tier/tag, regular price, optional special price and start/end dates, optional maximum price and date range, and auto-end/end-product flags. The UI posts update_product_prices_v2 with storeId=3, updateAll=1, and updateDD=0. A partial promo tuple is rejected: promo amount and both dates must be filled together or all empty. The PriceService updates local ProductInfo price/special-price fields and price logs, handles composed products, and for Menzzo products adds ProductPriceUpdateProcessor work through the legacy queue service. Therefore the JSON success response confirms the local service path returned; queue processing and any external catalog update remain separate evidence gates.

The menu's “Log de calcul des prix” page checks divers_divers_log_product_price_sheet_log and loads /product/product-price-sheet/paginate-price-sheet-log (product_price_sheet_log_paginate). Its filters and sort are applied by ProductPriceSheetLogRepository; rows show SKU, stock quantity, incoming quantity, sold quantity in 90 days, estimated months, stock status, chosen new price, and creation date. These are recalculation snapshots, not the detailed audit for a manual price change. The base-calculation table also has per-product change-history drilldowns (product_sheet_log and product_sheet_log_price) that show before/after effective price, user/time, and changed fields. The simulated-price edit writes to its own simulated-price log table.

The page render has a menu check; the table and mutation actions above do not repeat one in their controller methods. Do not treat that as proof of unauthenticated access: the application-wide security configuration was not established here.

Calcul du prix des produits ​

The route checks calcul_product_price. The user searches/selects a product in the Twig Select2 control, which queries ajax_search_product_sale_show, then submits productId back to the calculator route. NewProductService::getProductMenzzoPrice looks for a linked BigBuy product first and then a VidaXL product. The page shows that Menzzo price and Product::calculateDropPrice / calculateDepotPrice results; without tax-inclusive input the helpers round up 80% and 75% respectively. This screen only renders the calculation. No matching supplier product leaves the result blank; a stale/invalid product ID is not explicitly handled before the controller reads product fields.

Stock valuation and statistics ​

Menu labelSource scope and screen result
Valeur du stockGET /product-stock-value/index (product_stock_value_stock_value) reads the dated mz_product_stock_value valuation rows. If no date range is supplied, the repository defaults to yesterday.
État de stocksGET /product-qty-stat/ (products_qty_stat_index) lists the daily product quantity/availability statistics for yesterday by default.
Valeur du stock au 31/03/2021GET /product-stock-value-2021-03-31/index (product_stock_value_stock_value20210331) reads the 31 March 2021 snapshot entity/table.
Valeur moyenne du 04/22 au 08/23GET /product-stock-value-0104202231082023/index (product_stock_value_0104202231082023_stock_value) reads the named historical valuation table; the calculation command and detail query cover arrivals from 1 April 2022 through 31 August 2023 and the recorded valuation date is 31 August 2023.
Valorisation du stock déchargé entre 12/20 - 03/21GET /product-stock-value-2021-03-31-c1204/index (product_stock_value_stock_value20210331_c1204) returns product and container quantities/values from the C1204 historical entity.
Valorisation conteneurs 04/2021GET /product-container-stock-value/index (product_container_stock_value_stock_value20210331) lists the April 2021 container snapshot.
Debug valorisation du stockGET /product/calculate-product-stock-value opens a one-product/unit-value calculator; its POST companion returns a calculation without saving a valuation row.

The current valuation screen filters by manufacturer, product label, and subcategory, with column search/order and visible/all-row exports through /product-stock-value/paginate-product-stock-value (product_stock_value_paginate_product_stock_value). The repository joins the valuation row to product/store-3 price and manufacturer data, and returns date, on-hand quantity/value, average unit stock value, in-transit quantity/value, logistics-buffer value, and physical inventory. It also returns totals for the filtered valuation, transit, and logistics buffer. The page's default date comes from the repository's previous-day range; the calculation command menzzo:product:stock-value also defaults to the previous day and accepts an optional --date. That command builds a dated local snapshot from product quantity and container unit values, with separate in-transit and two-unit logistics-buffer calculations. The source does not establish how often the command is run.

A separate ProductController route, GET /product/product-stock-value (product_stock_value), still renders a similarly named live-product report, but it is not the destination selected by the current menu entry. It reads product rows and derives a cost breakdown from the first manufacturer’s CBM/USD bases, falling back to product CBM/USD, with CHEHOR, LOGIDAV, approach-cost percentage, margin, and Product::getStockValue(). The buying-price edit is rendered only for ROLE_ADMIN or IS_ALLOWED_BUY_PRICE. Keep this route's product-calculation and edit behavior distinct from the menu destination's dated valuation records.

The historical pages use distinct records, not the live valuation query. The 31 March 2021 screen displays SKU, quantity, and value from ProductStockValueAt20210331; the source includes a CSV import command for that table. The C1204 screen shows SKU quantity/value plus container quantity/value, and the April 2021 container screen shows SKU, quantity, and value. Their filters are manufacturer, label, subcategory, and table columns; exports pass the selected columns/format. The source snapshot does not establish that these historical tables are refreshed or reconciled today.

The 2022–23 screen adds arrival quantity and average arrival value. Its information icon calls /product-stock-value-0104202231082023/get-how-it-was-calculated with productId; the result is a list of arrived containers in the 2022-04-01 to 2023-08-31 window, including reference, arrival time, unit value, shipped quantity, and line total. An empty product ID throws rather than returning a normal empty result. The page and paginator call the general product_stock_value access check. The container page checks a distinct permission on its render route, while its paginator checks product_stock_value_stock_value; confirm effective user permissions before diagnosing a table load failure. The detail endpoint does not repeat a menu check in its controller method.

“Debug valorisation du stock” accepts an SKU and date. The POST /product/calculate-product-stock-value-by-sku uses the first manufacturer attached to that product, shifts the selected date to the first day of its month and then two months earlier for the CBM-base lookup, and asks CurrencyService for that currency's rate at the selected date. It returns buying price, volume, currency, rate, CBM base, manufacturer, and unit stock value, or success:false if a required product/manufacturer/base is unavailable. It does not persist the computed value.

The adjacent “Gérer les cbm et les devises” screen is a manual source for dated manufacturer bases. Its table can display/edit China, Turkey, India, and Poland CBM bases and the modal also submits euro, dollar, yuan, and a month label. /stock-value/add-stock-value maps submitted field names to ManufacturerBase labels, normalizes comma decimals, and creates/updates ManufacturerStockValue rows. The controller does not assign the submitted date (the setter is commented out), so the selected modal month is not shown as persisted by this action. The screen itself checks manufacturer_stock_value; its pagination and save actions do not repeat that check in their controller methods.

“État de stocks” defaults to yesterday and calls /product-qty-stat/paginate-products-qty-stat with date, DataTables filters/order, and optional export format. It returns product name/SKU, saved quantity, availability, sales without stock, and manufacturer names; the per-product chart calls /product-qty-stat/stat-graphpaymentby-product-and-createdAt with product ID and date and returns daily quantity points from the preceding month through the selected date. The local ProductQtyStat entity stores daily rows. Symfony command menzzo:product:stat fills missing daily rows for active products using current quantity/availability and sales-without-stock calculations (including composed-product sales). The source does not show its scheduler or confirm backfill completeness. The page, list, and graph actions call products_qty_stat_index.

Product monitoring ​

The Stock menu's “Veille des produits” has two pages: “Configuration” at GET /product/monitoring/ (product_monitoring_index) and “Rapport” at GET /product/monitoring/report (product_monitoring_report_index). Each page checks its corresponding menu permission. The AJAX methods do not consistently repeat those checks, so their effective protection must be verified against the application's security configuration.

In Configuration, the Select2 search requires at least three typed characters and calls /product/monitoring/search-product-monitoring for SKU/name matches. “Ajouter le Produit” sends the selected SKU to /product/monitoring/add-product-monitoring; an unknown SKU or one already under monitoring returns success:false,status:400, otherwise a ProductMonitoring record is created enabled. The list paginator returns SKU, title, categories, image, active/inactive switch, “Modifier les liens”, and a report link. Link editing loads current links using /product/monitoring/get-product-monitoring-info; each row stores a URL and mode (exact or approximatif). The UI requires non-empty URL fields before posting the list to /product/monitoring/add-monitoring-links; existing links are changed only when the link belongs to that product's monitoring record, otherwise a new ProductMonitoringLinks row is created. The source command consumes the URL and provider parser; it does not branch on the stored mode, which is shown in the report.

The switch calls /product/monitoring/switch-status-product-monitoring and toggles the record's status, which controls whether the command processes its links. Deleting one link calls /product/monitoring/delete-product-monitoring-link and deletes that link's history as well. “Retirez la veille du produit(s)” posts selected monitoring IDs to /product/monitoring/remove-product-monitoring; this removes each monitoring record and its links and histories. Both actions are destructive local deletes; the source UI does not show a confirmation step in the traced handlers. An empty bulk selection or an unknown single-link ID returns a 400-style JSON result; unknown IDs in a nonempty bulk selection are skipped.

The report's DataTables request to /product/monitoring/paginate-product-monitoring-report accepts column filters, sort/page, optional sku, historyId, and groupByProduct. It shows local product SKU/name/category/availability/price alongside each monitored URL's domain, external reference, mode, price, shipping, and availability. The repository joins monitoring links to history rows, so a link without history is omitted; it does not filter the report to enabled monitoring records. By default the controller displays the latest history row for each link. sku narrows the report; historyId filters the repository's joined history rows, while the controller still renders the latest values for any matching link. The group toggle combines link rows by product. “Ouvrir l'historique” calls /product/monitoring/get-product-monitoring-link-history and displays every stored row newest-first with external SKU, price, shipping, availability, and date. No history returns success:false,status:400.

The write-side history path is Symfony command menzzo:product:product_monitoring (ProductMonitoringCommand), not a Messenger handler. It acquires a command lock, loads enabled ProductMonitoring rows, skips malformed URLs, fetches each configured URL, and processes only HTTP 200 responses. It maps the URL host through monitoringDomains in app/config/config_menzzo.yml to a Symfony service implementing the scraping parser; for example, Vente-Unique and Beliani have processor classes in this source. Unknown hosts, failed/non-200 requests, empty parsed values, and per-link exceptions do not create a history row; exceptions are dumped by the command. When parsed SKU/price/shipping/availability differ from the latest history, it stores a new ProductMonitoringLinkHistory and records the host on a link whose domain is empty. Identical latest values are deduplicated.

For parsed prices, the command compares local Menzzo price to the external price and applies productMonitoringAlertPercentage thresholds (source-configured as 30 and 7 in this snapshot). Matches are aggregated into a ProductMonitoringAlert warning through SystemAlertService; that alert is a separate outcome from the report history. The inspected source identifies the command and provider map but not an invocation schedule, network success, active external-page compatibility, or alert delivery outcome.

Source limits ​

These paths describe the code at the pinned commit only. They do not prove the installed permission map, currently configured provider services, scheduled command cadence, price-queue consumption, current valuation/stat data, report freshness, or production behavior. For operator decisions, distinguish a page load, an accepted local write/queued item, a persisted history row, and a confirmed external price or monitoring result.

Source entry points ​

These links open the same pinned commit recorded above: menu routes, price-sheet controller, price update service, stock-value repository, stock-value command, monitoring controller, monitoring command, and monitoring provider map.

Internal developer documentation for Menzzo systems.