Skip to content

Arrival reservations and restock notifications ​

This page covers two different flows that use reservation data:

  1. Logidav publishes an arrival-reservation quantity to the French Magento store.
  2. Logidav imports customer reservations and notifies eligible customers when a container arrives.

The source snapshot is axelites/logidav@42116eb. The snapshot proves the local rules and handoffs. It does not prove that a command is scheduled, that a provider accepted a message, or that a customer received it.

Arrival-reservation quantity ​

The command is menzzo:products:sync-qty-arrival-reservation.

Candidate selection ​

The command scans products that meet all of these conditions:

ConditionSource rule
Ownersource = Menzzo and sourceId is present.
FulfilmentDropship is empty.
Availabilityavailable is one of 5_semaines through 16_semaines or >_16_semaines.
CompositioncompositionOffre is empty or ;; composed offers are not selected as roots.
Dateavailability must be an eight-digit Ymd value. Invalid or empty values are skipped.

The command has a lock, optional --limit, --batch-size (default 200), and --dry-run. It processes the normal candidate scan, then scans products carrying a stale nosell tag whose availability is no longer in the long-availability set and clears them.

Quantity and date rules ​

For a product with no future container, the command uses ProductService::getQtyArrivalReservationData(). For a product with a future container, it uses buildArrivalBasedReservationData():

  • The supplier's earliest processing command with remaining quantity wins when it has a positive remaining quantity.
  • When no positive supplier remainder exists, the published quantity is the fixed fallback 50.
  • A future-arrival product is eligible only when product.qty + upcoming non-arrived, non-canceled container quantity < 0.
  • The two branches calculate the date in a different order. The no-container branch takes Monday of the current week, then adds three months. The future-container branch adds three months first, then takes Monday of that week. Around month boundaries these can produce different Mondays. The command writes qty#dd/mm/yy.
  • Fourteen explicitly excluded SKUs receive no reservation update.

The source names the fallback in the report as used_fallback_qty_50; it is a rule in this revision, not a configurable value. A business owner should confirm that the fallback and exclusion list are still intended before changing or re-running the flow.

Tags and composed products ​

When a reservation is set, the command adds both nosell and reservation tags to the root product. When a reservation is cleared, it removes those two tags. For composed products, the command propagates the root quantity to eligible children, using the floored quantity. A child is skipped when it is ignored, has no sourceId, or is in the excluded-SKU list.

Magento handoff ​

The queue payload targets store 3 (France), identifies the Magento product through sourceId, and updates only qty_arrival_reservation. A quantity of 1 is converted to 10 before it is queued. Clears send the same attribute as null. Both actions use ProductDataUpdateProcessor with the at_5th_minute queue marker. The queue row is a local handoff; it is not proof that Magento accepted or displayed the value.

Dry-run mode writes a CSV under var/logs/reservation_dry_run_<timestamp>.csv with the SKU, product ID, arrival state, quantity/date, supplier remainder, fallback flag, reason and action. It skips queue creation, but this revision still mutates the managed product tags and calls flush() for each batch, so --dry-run is not database-read-only for tag fields. Treat the CSV as a preview of queue actions, and do not assume it can be run without a rollback or database backup.

Reservation import ​

The command menzzo:product-reservation:import imports reservations from the Magento Logidav API.

Defaults are status=confirmed, sent=0 and page-size=500. --sent accepts only all, 0 or 1; page size must be between 1 and 500. --from and --to are optional reserved_at bounds. The command locks, requests pages until totalPages, requires a successful API response and a reservation_id, then upserts by that ID inside one database transaction. It maps SKU, price, customer email/phone, availability weeks/date, timestamps, status, test flag and the local product image when the SKU exists. --dry-run validates and prints rows without inserting or updating them.

An unsuccessful API page, malformed result, or missing required field aborts the import. The source does not establish a partial-page commit policy: the normal path commits after all pages have been processed, and a caught error rolls back the transaction.

Restock notification eligibility ​

The direct resolveForContainer() path groups positive ContainerProduct quantities by SKU and accepts a paid-backorder provider. In this pinned wiring, PaidBackorderQtyProviderInterface aliases NullPaidBackorderQtyProvider, whose method returns 0; no real paid-backorder lookup is connected for this path. This does not describe every notification path. For each SKU this resolver path:

  1. subtracts the provider result (currently always 0 in the wired service);
  2. stops when the remaining coverable quantity is zero;
  3. walks confirmed reservations oldest first;
  4. treats each reservation row as quantity 1 in this revision;
  5. skips reservations already represented by a notification row; and
  6. skips customers opted out of every channel globally or for that SKU.

The resolver is read-only. It does not mint coupons or send email/WhatsApp. A reservation can be returned once even when the same SKU is present in multiple container rows; rows are aggregated before allocation.

There are two candidate inputs in this class, and they must not be conflated. resolveForContainer() aggregates entity rows and then calls resolveForSku(), which uses the injected paid-backorder provider described above. Wave 1 uses resolveFromCandidates(), which consumes pre-fetched incoming_qty and backorder_qty and subtracts the supplied value without calling that provider. The candidate query sums matching sale-product quantities with a container; it does not establish that they are all paid back-orders. The effective deduction therefore depends on the caller and its supplied data.

Wave 1 and Wave 2 ​

menzzo:restock:notify-wave1 is the morning campaign. It resolves eligible reservations, creates one notification row, creates a coupon and short URLs, then attempts Email 1 and WhatsApp. A notification row makes the reservation-wide operation idempotent on a rerun. The command supports --day, --limit, --dry-run, and a dry-run-only --export containing SKU and customer email.

menzzo:restock:notify-wave2 is the evening reminder. It considers only notifications touched by Wave 1 for that campaign day. Before sending Email 2 or retrying WhatsApp, it skips a reservation when:

  • it was already called by phone;
  • a matching processing/complete sale was found after the notification was created;
  • the coupon expired;
  • the SKU is not active, visible (visibility 2 or 4) and linked to a source;
  • the customer opted out of all channels; or
  • Wave 1 did not mint a coupon.

Email and WhatsApp have separate sent/error fields. Email 2 can be skipped while WhatsApp remains eligible, and a WhatsApp provider failure can be retried. The wave report distinguishes skipped reasons from provider failures.

The coupon discount defaults to 20% and its configured validity is at least one hour; the source's constructor default is 24 hours. The campaign expiry is built from the campaign day, while the actual provider response remains an external boundary.

Receipt WhatsApp and opt-outs ​

menzzo:product-reservation:receipt-whatsapp allocates each SKU's sellable stock to pending reservations under a row lock, subtracting active 24-hour holds. Each claim receives a 24-hour hold and an attempt count. It then creates or reuses a coupon and short URLs, sends a Twilio template, and records sent/error state. It validates --limit and positive --coupon-ttl-hours; --dry-run rolls back local writes and skips external calls.

menzzo:restock:sync-optouts imports opt-out rows from the Menzzo API. It resumes from the stored numeric cursor, or from the previous 24 hours when no cursor exists, upserts by Menzzo ID and falls back to customer/email/SKU/scope/channel, then persists the cursor after each page. The source does not establish the external API's schedule.

Diagnosis boundaries ​

SymptomCheck firstDo not infer
No arrival reservation valueCandidate filters, valid Ymd availability, future-arrival delta, exclusion/ignore reason and dry-run CSV.A missing queue row means Magento rejected it.
Wrong reservation quantitySupplier remainder, fixed fallback 50, upcoming container sum and composed-child propagation.The quantity is a business configuration value.
Customer not notifiedResolver allocation, notification row, opt-out scope/channel, purchase check, product buyability, sent/error fields.A local sent timestamp proves provider or customer delivery.
Duplicate notificationExisting notification row and wave/channel sent fields.A retry is safe across every provider operation.
WhatsApp not sentE.164 validation, opt-out, active hold, short URL/coupon and Twilio error.Email and WhatsApp share one eligibility result after the channel gates.

Source links: arrival quantity command, reservation import command, reservation calculations, eligibility resolver, null paid-backorder provider, service wiring, notification orchestrator, wave 1 command, wave 2 command, receipt WhatsApp service, and opt-out sync.

Internal developer documentation for Menzzo systems.