Skip to content

Customer Service and Payments ​

This page maps the legacy Symfony menu entries for carts, payment follow-up, refunds, and SAV (after-sales cases) to their controllers and data paths. It describes the source snapshot supplied for this task, identified as axelites/logidav commit 88d9180215a5d489407498fe4905fdd38efd756d; source review does not establish what is currently deployed.

Access and source boundaries ​

The menu builder creates payment and SAV submenus for the menzzo and meublePromo sources. The non-tree menu is filtered against the user's menu-access list, with a ROLE_ADMIN exception; many page actions also call mz.menu_provider->hasAccess(). That menu filtering does not prove that every AJAX route has the same route-level check, so use the page-specific access path described below rather than treating a hidden menu item as an authorization boundary.

The endpoint examples below use Symfony route names and the path suffixes declared on the controller methods. Their controller-level prefixes are cart, refund, presto, reconciliation-of-payments, sav, stat, and statistic; any deployment-level prefix is not established here.

Carts, contact follow-up, and SMS ​

Menu entryRoute and inputWhat the source does
Tous les panierscart_index, GET /cart/; list data uses paginate_cartsRenders the shared cart table. The default date interval is today; filters include date, cart ID, called flag, call status, and reason. The table is backed by AbandonedCart and its repository.
Paniers abandonnées avec téléphone / sans téléphonecart_with_phone, cart_without_phone; both use the shared cart view and paginate_cartsSelects the phone/no-phone view mode. The default date interval is the previous month through today. The same call-tracking fields are available; the phone page also supplies payment-method labels.
Annulation avant paiementcancellation_before_payment, also the shared cart view and paginatorUses the cart list with the before-payment mode and the previous-month date default. It remains a cart follow-up screen; the route itself does not issue a payment refund.
Annulation avant expéditioncancellation_before_shipment, data via cancellation_before_shipment_paginateThis list is built from Refund rows with autorisation = 1 and the fixed pattern “Annulation avant expédition”. Its update route records call status, failure reason, recall time, caller, and time on the refund row; to_recall creates a Notif. This is follow-up on an authorized refund record, not proof that an order was canceled or money returned.
Rapport paniers abandonnéescancellation_before_shipment_report; data via cancellation_before_shipment_report_paginateDefaults to the previous seven days. cart_range accepts a displayed d/m/Y - d/m/Y interval; results come from LogReport and include user, order increment ID, amount, action, and date.
Sms Paniers abandonnéescart_with_phone_sms; table via paginate_carts_smsLists phone-bearing carts for SMS work. The page defaults to the previous month through today and a grandTotal threshold of 300; available SMS rules come from mz.config.
Sms commandes en attente de paiementsale_pending_payment; table via sale_pending_payment_paginateQueries pending-payment sales and renders customer, order, products, shipping, coupon, and SMS-history fields. The page also defaults to the previous month through today and a grandTotal threshold of 300. Rows emit a real-shipping-method edit control and the template loads sales.js with the realShipment choices, but it does not define the sale_update_real_shipment route variable used by that handler; the source does not establish that this screen can submit the edit. The backend POST /sale/update-real-shipment updates the sale method/description and log, rejects CSV-exported sales, and may send a retrait email, but that backend behavior alone does not prove this page is wired to it.
Résultats d'envois des SMScart_to_sale_through_sms; data via paginate_cart_to_sale_through_smsThe repository joins carts to sales through sale_id_after, requires send_sms to be present, and accepts a storeId filter. It is a result view, not a delivery receipt from the SMS provider.

Cart call updates use update_cart with cart ID, called flag, status, reason, order reference, and optional recall time. The controller saves the cart and an AbandonedCartLog; an order reference also creates a LogReport, and to_recall creates a notification. Marking a cart deleted uses remove_cart; cart_check_order asks the sale service to check for an order and marks the cart deleted only when a match is returned. A cart-log lookup requires a numeric ID and returns HTTP 400 for an invalid query ID; repository errors return HTTP 500.

Selecting carts and submitting cart_send_all_sms is an external communication path. The POST carries cart IDs, rule ID, and a polling token; the controller records “SMS scheduled” logs and starts menzzo:abandonedcarts:sms. That command obtains the configured SMS text and coupon, calls AbandonedCartService::sendSms(), and records the send result on the cart. Missing or invalid phone numbers, missing coupon, or a failed SMS send return an error result. The SMS provider implementation and actual delivery receipt are outside this path. The POST action itself does not call hasAccess() in its method; broader firewall authorization was not reviewed.

When a call is marked do_not_answer, update_cart attempts to enqueue the ChatwootWhatsappTemplateProcessor with the abandoned_cart template key and records a cart log. A missing phone or queue exception produces an error response; enqueueing does not establish that WhatsApp delivered the message.

Refunds and payment operations ​

Menu entryRoute and inputWhat the source does
Remboursements / Pré-remboursementsrefund_index at /refund/index/{source} and refund_pre at /refund/refund-pre/{source}; source branches are menzzo and meublePromoThe corresponding paginators query Refund rows by source, authorization state, and table filters. The main refund list filters for autorisation = 0; the pre-refund list filters for autorisation = 1. List visibility can be narrowed for users with only REFUND_LESS_THAN_100_EURO. See Refunds and SAV for the provider-specific submission and reconciliation paths.
Create a pre-refund from an orderrefund_add_pre_refund (POST, CSRF required; ROLE_ADMIN or PRE_REFUND_FROM_ORDER)Resolves the sale from incrementId, records amount/type/reason/information and payment details in a Refund, sets status 0 and authorization 1, creates a linked SaleSav, and writes a SaleLog. Loyalty vouchers are rejected here and must use their dedicated import path.
Submit a refundpre_refund_send (POST)Loads the refund and sale, rejects missing records, loyalty-voucher rows, unsupported/non-Menzzo payment methods, and some manually paid orders. Supported provider branches call provider-specific code and persist provider-specific results. HTTP/status outcomes are not a single shared contract; see the linked refund page.
Resolve or generate a manual refundresolve_refund, generate_manually_refundBulk resolution writes a manual resolution message and sets refund status to 1; manual generation sets the “Remboursement manuel” note, clears authorization, and marks the row complete. These local state changes are not provider confirmation.
Statistiques / Almastat_payment at /stat/payment; statistic_alma_payment at /statistic/almaThe first reads payment and Payline statistics for a submitted date range. The Alma page uses a date range and a sale-repository aggregate. Both render reports; the code does not submit a payment.
Commandes Prestopresto_index at /presto/; data via /presto/paginateLists SalePresto records with linked sale date/order status and stored Payline status. The row action status_payllne_presto (POST, id) calls Payline getTransactionDetails(), then stores the response, status text, and isTreated = 1. Provider exceptions and retry behavior are not handled by a common contract in the inspected action.
Rapprochement des paiementsreconciliation_of_payments_index and reconciliation_of_payments_import_files_index under /reconciliation-of-payments/The order table uses SaleRepository::searchReconciliationOfPaymentsSaleList() and can export visible columns. The import page lists FilesImport records; its inherited upload handler accepts a files collection and stores records under the reconciliation import folder. The import command processes pending/processing records, updates status, and saves line/error data. A successful upload therefore means a file was recorded, not that reconciliation completed. File-format validation, command schedule, and deployed processing status were not established here.
Payline / Ezynesspayline_ezynesss_mapping, /sale/payments/payline-ezyness/mappingThe view reads mapping rows through PaylineTransactionLogService, backed by mz_payline_commands_mapping joined to mz_sale; the table can search, sort, and paginate increment/order, transaction, and amount fields. The controller contains two methods declaring the same route name and path; route-registration precedence and which view action runs at runtime are unresolved.

Payment menus also include source-scoped refund exports and provider-specific status/detail routes. Use the route names from src/AppBundle/Menu/Builder.php and the individual controller methods; do not infer a common refund lifecycle across providers. A refund row, a manual resolution, a local “complete” flag, and a provider's settled transaction are distinct evidence.

SAV case handling ​

The SAV submenu is also generated for menzzo and meublePromo. Its main route family is sav_index at /sav/index/{source} and shares SaleSav search/pagination with the state-specific screens. Menu variants are grouped below; their repository methods define distinct result sets rather than separate case entities.

Menu entryRoute familySource-backed use
Liste SAV / Tous les SAVsav_index, sav_allPaginated case lists from SaleSavRepository::search() and searchAllSav(). The page supplies manufacturer, communication-type, and agent filter choices.
SAV validéssav_validatedUses searchSavValidated() for the validated subset.
Demandes d'annulation avant expédition / SAV annulation avant expéditionsav_cancelation_request, sav_canceledSeparate request and canceled lists use searchCancelationRequests() and searchSavCanceled(). They should not be treated as interchangeable statuses.
SAV relances / SAV Prioritaires / Échange SAV à validersav_relaunch_request, sav_priority, sav_exchangeThree separate repository queries (searchRelaunchRequests, searchPrioritySav, and searchEchangeSav) drive follow-up, priority, and exchange queues.
Pièces détachées → Import des pièces détachées Asirsale_sav_asir_spare_parts_uploadThe upload page is followed by the FilesImportService upload/status routes and AsirSparePartsService line, dimension, order, and SKU actions. Uploading is not proof that imported parts were matched or applied.
Exportsav_sav_exportReads source, date range, date type, SAV status, and problem-type filters; queries SaleSav and exports an XLS result.
Export performance SAVsav_sav_performance_exportOnly displayed for ROLE_ADMIN or SEE_SAV_PERFORMANCE_EXPORT. The action validates its date range and starts a console-backed export/polling flow; invalid dates return HTTP 400. Completion is represented by the returned token data, not by the menu click.
Planning SAV / Planning SAV testssav_planing_dev, sav_planing_testThe legacy menu uses these exact route names. Their event endpoints call SavPlanningService with separate normal and TEST data scopes; create/update/delete actions can change planning records and treated status. The old sav_planing route is commented out in the menu.
Chat AIsav_chat_aiThe screen posts a message to OpenAi::openaiCompletion(), returns text, and stores the request, result, user, and timestamp in ChatAiLog. The report-menu history and stats read these logs. The source does not establish retention, redaction, or an approved policy for customer-identifying text; do not treat this page as a customer-safe channel without that policy.
Tous les SAV de testsv2_index?redirect_to=app.sav.archived_testThis is a Symfony-to-new-Logidav bridge target, not one of the legacy SaleSavController list actions; target behavior is not in the supplied source tree.

The active list UI also supports bulk agent assignment. It keeps explicitly selected SAV IDs across pages in browser cookies for seven days, and the bundled script submits that ID list to POST /sav/bulk-assign-agent (sav_bulk_assign_agent). The service also accepts an all-selected selection object with filters and exclusions, but that fallback is not sent by the bundled script. The action requires mz.menu_provider->hasAccess('sav') and a valid agent and nonempty selection (400 for missing input, 404 for an unknown agent). SaleSavService::bulkAssign() updates SaleSav.user, skips unchanged assignments, writes a SAV log with the acting user and previous/new agent, and creates a manager-side SaleSavMessage that marks the conversation unresolved. A successful response reports updated and skipped counts; it does not change the SAV status.

The case screens and their mutation endpoints are implemented primarily in SaleSavController and SaleSavService. The refunds and SAV workflow page separately documents the verified fragments for SAV API intake, provider refund submission/reconciliation, and known physical-return/stock gaps. A SAV case, a cancellation request, an exchange queue, and a refund are separate records and transitions in the inspected code.

Evidence limits ​

The legacy route, controller, and service behavior above was reviewed at the immutable commit recorded in source_refs. New-Logidav destinations, live menu visibility, deployed route registration, provider delivery/settlement, and owner acceptance were not checked. The deeper implementation of payment and SAV actions remains provider- and state-specific as linked above.

Source entry points ​

Internal developer documentation for Menzzo systems.