Skip to main content

Sales Import Workflow

This page documents the core LogiDAV sales ingestion workflow for Menzzo Magento 2 orders.

It is primarily implemented by:

Why This Workflow Is Critical​

This workflow is one of the highest-risk paths in the system because it:

  • imports new revenue-bearing orders into LogiDAV
  • updates local order status from Magento
  • can decrement stock when orders move to processing
  • triggers downstream operational behavior such as RDV handling
  • can affect customer communication for RETRAIT flows

If it breaks or becomes partially inconsistent, the impact can include:

  • missing or duplicated orders
  • incorrect order status in LogiDAV
  • stock drift after late payment or status changes
  • customer communication mismatches
  • operational teams working from stale order data

Main Components​

  • Command: src/AppBundle/Command/Sale/V2/SaleCommand.php
  • Command: src/AppBundle/Command/Sale/V2/CheckSaleImportDisabledCommand.php
  • Command: src/AppBundle/Command/Sale/V2/UpdateSaleCommand.php
  • Service: src/AppBundle/Services/SaleService.php
  • Monitoring service: src/AppBundle/Services/SaleImportMonitoringService.php
  • Stock mutation service: src/AppBundle/Services/ProductQtyLogService.php

Workflow Overview​

Status Synchronization Overview​

Detailed Behavior​

1. New Order Import​

menzzo:v2:sales does the following:

  1. Uses LockableTrait to prevent concurrent execution.
  2. Ensures the MZ_ACTIVATE_SALES_IMPORT config row exists.
  3. Stops with Sales import is disabled! if SaleService::canImportSales() returns false.
  4. Reads the cursor from SaleService::getLastMagentoId().
  5. Fetches Magento orders with entity_id > last imported ID.
  6. Imports each order via SaleService::addOrder($sale, true).
  7. Advances the stored cursor with SaleService::setLastMagentoId($magentoId).
  8. Runs RDV handling for imported sales that end in processing or complete.

menzzo:v2:sales does not create or resolve disabled-import alerts. That work belongs to menzzo:v2:sales:check-disabled-import.

2. Disabled Import Monitoring​

menzzo:v2:sales:check-disabled-import must run every 30 minutes and does the following:

  1. Ensures the MZ_ACTIVATE_SALES_IMPORT config row exists.
  2. If sales import is disabled, calls SaleImportMonitoringService::checkAndNotifyIfDisabledTooLong().
  3. If sales import is enabled, calls SaleImportMonitoringService::resolveOpenAlertIfImportEnabled().
  4. Exits without launching Magento import, reading SaleService::getLastMagentoId(), or importing orders.

Alerting starts only when the config remains disabled for more than 30 minutes, measured from the row updated_at value. The alert is deduplicated while open, and it is resolved automatically when import is re-enabled and the monitoring command runs.

Teams notification is optional and non-blocking. If MZ_TEAMS_ALERT_WEBHOOK_URL is empty, no Teams message is sent; Teams delivery failures are logged and do not block the system alert path.

3. Create vs Update Semantics​

SaleService::addOrder() is not only a create method. It acts as an upsert:

  • if the sale does not exist locally, it creates the sale and sale products
  • if the sale already exists, it updates sale status, billing email, order payload, and missing items

That means both commands depend on the same normalization and persistence path.

4. Stock Side Effects​

Stock mutation happens in two different ways:

  • during sale import, when a newly created or newly completed/processing sale is persisted
  • during status synchronization, when a Magento status transition moves a sale into processing

The stock change is logged through ProductQtyLogService::productChangeLog(...), which is why this workflow needs careful documentation of side effects and replay behavior.

5. RDV and Customer-Facing Side Effects​

The import and update flow can call handleRdv($sale) for relevant sales.

The update flow also attempts special handling when:

  • the new Magento status becomes processing
  • the real shipping method is RETRAIT

In that branch, it calls sendRetraitEmail($sale). In the current code, sendRetraitEmail() returns immediately, so the operational intent exists even though the email logic is effectively disabled in the present implementation.

6. Delta Recovery Pass​

menzzo:v2:sales:update includes a final delta fetch of Magento orders updated in the last 48 hours. This appears intended to catch out-of-band status changes such as late payment-related updates that may fall outside the primary status buckets.

That recovery pass is important operationally because it reduces the chance of stale local order state after delayed upstream updates.

Inputs and Outputs​

Inputs​

  • Magento order payloads from mz.sale.api
  • local cursor from config (last Magento sale id)
  • local sales filtered by date range and status

Outputs​

  • created or updated Sale and SaleProduct records
  • updated config cursor
  • SaleLog entries for status synchronization
  • stock movement logs
  • system alerts on import failures
  • one critical SaleImportDisabledAlert when MZ_ACTIVATE_SALES_IMPORT remains disabled for more than 30 minutes
  • optional Microsoft Teams notification through MZ_TEAMS_ALERT_WEBHOOK_URL, with Teams config bootstrapped by the monitoring notification path if missing
  • RDV handling side effects

Operational Risks​

Cursor Risk​

menzzo:v2:sales advances the Magento cursor after each successfully imported order. If an order fails, the command skips it and continues. This is safer than aborting the full batch, but it also means operators need a clear replay strategy for skipped orders.

Partial Replay Risk​

Because SaleService::addOrder() is reused for both create and update behavior, replaying a payload is not a pure no-op. It may:

  • update status
  • add missing sale items
  • trigger stock movements for newly created sale products in active statuses

Upstream Status Drift​

menzzo:v2:sales:update is responsible for reconciling LogiDAV with Magento, especially when upstream payment or workflow events arrive later than expected.

Deadlock / EntityManager Failure Handling​

The import command explicitly downgrades some failures such as deadlocks or closed entity manager cases to warning-level alerts and continues processing the batch.

Disabled Import Alerting​

MZ_ACTIVATE_SALES_IMPORT is the feature flag controlling LogiDAV sales import. If its config row is missing, both menzzo:v2:sales and menzzo:v2:sales:check-disabled-import recreate it idempotently with default value 1 (enabled). This default is intentional: a deployment or config corruption must not leave menzzo:v2:sales unusable. Existing values are never overwritten, so this does not change import behavior when the row already exists and a voluntary /config disable remains respected.

Disabling the import is allowed during deployment or operational work. No alert is sent immediately. Alerting starts only when the config remains disabled for more than 30 minutes, measured from the row updated_at value. If that timestamp is missing or malformed, monitoring logs/skips alerting rather than crashing the cron.

The import-disabled alert is deduplicated through SystemAlertService::add() using model SaleImportDisabledAlert::class and stable info {type: sale_import_disabled, config_code: MZ_ACTIVATE_SALES_IMPORT}. One open alert is expected for that stable info key. As long as the alert is open, repeated cron executions do not send repeated email or Teams notifications.

When the import is reactivated, the next menzzo:v2:sales:check-disabled-import run resolves matching open disabled-import alerts automatically. This lifecycle is intentionally owned by the monitoring cron; manual resolution is not the only recovery path.

Teams notification is optional. MZ_TEAMS_ALERT_WEBHOOK_URL is created idempotently by the monitoring notification path if missing, and an existing webhook value is never overwritten. If the row is empty, no Teams notification is sent. Teams delivery failures are caught and logged so they never block the cron or the SystemAlert email flow.

What Good Documentation Must Cover​

For future maintainers, this workflow should always document:

  • where the import cursor is stored and how to reset it safely
  • how SaleService::addOrder() behaves on create vs update
  • when stock is decremented
  • when RDV handling is triggered
  • whether RETRAIT email behavior is active or intentionally disabled
  • how to replay skipped or failed Magento orders
  • which logs and alerts operators should inspect first