Appearance
Loyalty voucher import
This workflow turns an approved CSV row into a customer loyalty coupon and a linked local refund/SAV record. It is financial and customer-facing: a Magento coupon, a local refund record, an SAV link and the confirmation email are separate outcomes. The source revision proves the behavior of this code; it does not prove that the command is scheduled or that a customer received or used a coupon.
Who can use it and how it starts
The controller protects the import routes with ROLE_ADMIN, PRE_REFUND_FROM_ORDER or LIST_REFUND_COUPON, and requires a CSRF token for upload, selection and confirmation. The CSV route accepts a valid .csv upload up to 2 MiB. The preview service limits a batch to 100 data rows and stores the file SHA-256 to detect an active duplicate file.
The current source has two possible intake models:
| Intake | Source behavior | Availability boundary |
|---|---|---|
| CSV upload | The first two columns are order increment ID and amount. Five-column rows add uses-per-customer and dates; six-column rows also add uses-per-coupon. Invalid rows remain visible in the preview. | The controller handles the CSV route. A file is not processed merely by uploading it. |
| Order selection | Selection and percentage configuration routes exist, but loyalty_voucher_selection_enabled must be true. Confirmation rejects selection imports when the service flag is disabled or when the simple action is not percentage. | The source default/runtime value is not established here; treat selection as unavailable until configuration is verified. |
Flow at a glance
View diagram source
flowchart TD
A[Authorized operator uploads CSV or prepares selection] --> B[Preview and validate rows]
B --> C{Eligible and selected?}
C -->|No| D[Keep invalid, already or manual result]
C -->|Yes| E[Confirm under transaction and queue lines]
E --> F[Process command claims lines with a lock]
F --> G[Reserve local coupon/refund operation]
G --> H[Create or inspect Magento coupon]
H --> I{Remote settings verified?}
I -->|No or uncertain| J[Retry or manual; do not create refund/SAV]
I -->|Yes| K[Create or finalize local refund and SAV]
K --> L[Write history and send confirmation email]
Preview and eligibility rules
Each row must resolve to exactly one local sale. An empty or ambiguous increment ID is rejected. Eligible sales have status processing or complete, source Menzzo or MeublePromo, at least one sale product and a valid customer email. The amount parser accepts a positive amount with zero or one/two decimal places and rejects values above the configured integer limit. The reviewed fixed-amount CSV path does not compare the amount with the order grand total.
The preview rejects duplicate order IDs within the same file, invalid column counts, invalid uses/date options, an already queued order, an active duplicate file and a prior unresolved operation. If an existing loyalty-voucher refund is found, the service checks its type, sale link, SAV link, coupon code and amount/percentage before marking the row already. Mismatched records are rejected; multiple vouchers require manual review.
For a two-column row, the source defaults the coupon dates to today through one year from today, with one use per coupon and one use per customer. Supplied dates use JJ/MM/AAAA; omitted dates use the defaults. The end date must not be expired and Du <= Au. Percentage discounts require the selection path and one use per coupon/customer.
Confirmation and queue boundaries
Confirmation locks the operator and the import, re-checks ownership and status, and validates the selected line IDs. For selection imports it also checks the selection revision, order identity, customer email and (for fixed-cart rules) the saved grand total. Selected valid lines move to a per-user active queue; the import becomes pending. No selected lines leaves the import completed or completed-with-errors.
The processing command is menzzo:loyalty-vouchers:process-import. It uses a command lock, runs the queue runner for at most 50 seconds, claims pending lines under a pessimistic lock and marks the import processing. A stale claim older than 15 minutes can be recovered; once a line reaches the maximum attempt count it becomes manual review. The installed schedule and worker cadence are not established by this page.
Local reservation, Magento verification and completion
The row processor first re-checks sale eligibility, then reserves a coupon code and local operation under a sale/line lock. New codes use the FIDELITE prefix and are checked for uniqueness against the local refund repository. After an uncertain prior attempt the reserved code is retained: an absent prior state goes to manual review, while an unknown state is inspected again using the same code.
For a new reservation, the Magento service creates a specific coupon using the configured amount or percentage rule, then inspects the coupon. It verifies the coupon type, action, amount/percentage, shipping application, website and customer-group sets, date range and usage limits. A successful HTTP call alone is not enough. Creation or inspection failures can leave remote state uncertain and are retried or escalated according to the line state.
Only after Magento verification does completion create or finalize the local LOYALTY_VOUCHER refund, link a validated SAV, write a sale log and history row, and mark the line created. It then sends the localized confirmation email. If the local record is committed but email sending is not confirmed, the line remains eligible for a retry; this is a local/email boundary, not proof of delivery to the customer.
Duplicate and failure decisions
| Observation | Result |
|---|---|
| Same file is already active | Reject the new preview/confirmation; inspect the existing import. |
| Same order appears twice in a file or another queued import | Keep the first operation and mark the duplicate invalid. |
| Multiple loyalty refunds or a mismatched coupon/SAV | Multiple vouchers require manual review; mismatched records are rejected. Do not automatically create another coupon. |
| Magento coupon absent after an uncertain attempt | Treat the state as unknown and keep the reserved code; do not issue a second code. |
| Claim changed or expired during processing | Manual/retry path; the losing worker must not call Magento or finalize the refund. |
| Provider/API or database exception | Retry with bounded backoff; after the maximum attempts the line is manual. |
What to verify operationally
For an incident, inspect the import status, line status, claim/attempt timestamps, coupon code, Magento verification status, refund ID, SAV ID and email status together. Check Magento coupon details and local refund/SAV state before any replay. Do not infer customer delivery or coupon use from created, a local refund status or a successful HTTP response.
Related context: refunds and SAV, payment issues, and the customer service and payments interface.