From 29e77a973bb17f9071ee2de73da3bdb2639c9ca3 Mon Sep 17 00:00:00 2001 From: Konstantinos Arvanitakis Date: Tue, 1 Sep 2026 13:34:37 +0300 Subject: [PATCH] Feat: Orders Feature Survey --- docs/scratch/orders-feature-survey.html | 450 ++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 docs/scratch/orders-feature-survey.html diff --git a/docs/scratch/orders-feature-survey.html b/docs/scratch/orders-feature-survey.html new file mode 100644 index 0000000..ac10bd1 --- /dev/null +++ b/docs/scratch/orders-feature-survey.html @@ -0,0 +1,450 @@ +Order Feature Survey + + + +
+ +
+
boboko / order · competitive survey
+

What order management elsewhere can do that boboko can't yet

+

+ Where the Checkout survey stopped — the instant Order exists — this + one starts. A feature-by-feature pass across Shopify, WooCommerce, PrestaShop, and + (briefly) Magento's post-placement order layer — sourced, not recalled from memory — + checked against what Lunar's Order model and the + already-shipped Filament ManageOrder page actually support + today. For deciding what the new Order module needs to own, not a + build order. +

+
+ ● have + ◐ partial + ○ missing +
+
+ +
+
+ 01 +

Status model

+
+

One field, or several axes — and who's allowed to move it.

+ +
+
Payment status independent of a single overall status
+ partial +
The data exists — ManageOrder::paymentStatus() derives a real value from transactions()/captureTotal()/refundTotal()/intentTotal() — but it's a computed display value on the admin page, not a stored column or something the rest of the system (mailers, automations) can key off. Shopify and Magento both make payment status a first-class, independently-queryable dimension; here it's derived on the fly, once, in one Filament page.
+
+ +
+
Fulfillment status independent of overall status
+ missing +
No equivalent of paymentStatus() exists for shipment/fulfillment state — Order has no shipments() relation of its own at all; it's added dynamically by Modules\Core\Shipping\Providers\ShippingServiceProvider::resolveRelationUsing(), outside Order's own boundary (see docs/checkout.md, "Where Order would likely absorb work"). Every platform researched (Shopify, Woo, PrestaShop, Magento) treats "has this shipped" as derivable from child records, not a manually-set field — Lunar has the child records (Shipment) but no derived status method reading them.
+
+ +
+
Staff-editable order status with a picker/action
+ have +
ManageOrder ships a working UpdateStatusAction out of the box, backed by config('lunar.orders.statuses') — a flat, merchant-configured list, each entry carrying a label/color/favourite flag. Closer to WooCommerce's single linear field than Shopify's multi-axis split.
+
+ +
+
Status rows carry behavior (auto-send email, generate invoice, restock)
+ partial +
Each status entry in config('lunar.orders.statuses') already declares mailers and notifications arrays — the PrestaShop-style shape is there in config — but per the Checkout survey's finding, nothing in core actually reads and dispatches from those keys on a transition. The data model for "status carries behavior" exists; the behavior doesn't.
+
+ +
+
Order-status-changed event other code can react to
+ missing +
Same gap the Checkout survey flagged for order creation: no OrderStatusUpdated/equivalent exists anywhere in core. UpdateStatusAction just writes the column. Anything wanting to react to a status change — a confirmation email, a webhook, re-deriving payment/fulfillment status — has to hook the raw Eloquent Order::updated() event and diff status itself.
+
+
+ +
+
+ 02 +

Fulfillment & shipment tracking

+
+

Turning a placed order into a package that moves.

+ +
+
Shipment as its own record, separate from the order
+ have +
Modules\Core\Shipping\Models\Shipment (carrier, tracking reference, label-printed timestamp, manifest reference) already exists and belongs to Order. Built this session, ahead of most gaps in this survey — the record shape is closer to Magento's per-shipment entity than Woo's "no shipment entity at all."
+
+ +
+
Multiple shipments per order (partial/split fulfillment)
+ partial +
Shipment has no quantity-per-line or order_line_id concept — it's one shipment record per carrier voucher, with a parent_reference for ACS's own multipart-voucher case (one physical order split into multiple packages by the carrier), not a per-line-item fulfillment split decided by staff. Closer to "multiple packages for one shipment" than Magento's true per-line partial-shipment model.
+
+ +
+
Create-shipment action from the order admin screen
+ have +
Modules\Core\Shipping\Extensions\OrderViewExtension adds a working "Create Shipment" header action to ManageOrder, resolving a CarrierFulfillmentInterface by the order's chosen shipping method and calling createShipment() — genuinely wired, not a stub. Currently lives under Shipping, flagged in docs/checkout.md as conceptually an Order concern.
+
+ +
+
Tracking number + carrier surfaced on the order itself
+ have +
Shipment.tracking_reference/carrier exist and are populated by createShipment(); PollShipmentTrackingJob (scheduled every 30 minutes) keeps ShipmentInfo checkpoints current via CarrierFulfillmentInterface::trackShipment(). Genuinely ahead of PrestaShop's thin order_carrier.tracking_number field — this has a real checkpoint history, not just one string.
+
+ +
+
"Shipped"/"delivered" status auto-derived from tracking
+ missing +
The tracking checkpoints exist (ShipmentInfo, TrackingStatus enum including Delivered) but nothing writes them back onto Order.status — a delivered shipment doesn't move the order out of whatever status it was already in. Every platform researched treats "delivered" as a status a customer/staff can see on the order, not something buried one relation away.
+
+ +
+
Shipping/delivery notification emails (shipped, out-for-delivery, delivered)
+ missing +
Research: Shopify fires four separate templated notifications across this window alone (shipping confirmation, out-for-delivery, delivered, plus edited-order). None of the pieces exist here — no order-status-changed event (01) to trigger from, and no mailer wired to PollShipmentTrackingJob's own status updates either.
+
+
+ +
+
+ 03 +

Payments: capture, refund, cancellation

+
+

Money moving back out, and orders that never should have been placed.

+ +
+
Refund action from the order screen, amount-scoped
+ have +
ManageOrder's refund action already exists — picks a transaction, an amount (validated against availableToRefund()), and notes, then calls the driver's own Transaction::refund(). This is genuinely native, matching Woo/Magento's line-item-adjacent (if not line-item-exact) refund UX.
+
+ +
+
Capture action for auth-then-capture payment flows
+ have +
ManageOrder's capture action + requiresCapture()/canBeRefunded() guard methods already exist, delegating to Transaction::capture() — this is the Stripe "authorize now, capture later" flow's admin-side half, already built ahead of most gaps here.
+
+ +
+
Refund tied to specific line items (not just a dollar amount)
+ missing +
The refund action takes a transaction + amount, with no line-item selection or restock decision — WooCommerce and Magento both make "which items, how many, restock or not" the primary refund UI; here it's one number against one transaction, closer to a manual adjustment than a structured partial return.
+
+ +
+
Order cancellation as a distinct action (vs. just changing status)
+ missing +
No dedicated "cancel" action exists on ManageOrder — a cancellation today would just be picking a "cancelled"-labeled entry from the generic status dropdown (01), with no automatic refund trigger, no stock-release logic, and no distinction from any other manual status edit.
+
+ +
+
Refund/capture reflected back into an order-level payment status
+ partial +
Same gap as 01's payment-status finding — paymentStatus() recomputes correctly from transactions when the admin page loads, but a refund doesn't push the order into a refunded/partially-refunded overall status the way Shopify's displayFinancialStatus does automatically.
+
+
+ +
+
+ 04 +

Returns (RMA)

+
+

The one area every researched platform treats as optional, not core.

+ +
+
Return-merchandise-authorization flow (customer requests, staff approves)
+ missing +
No Return/RMA model, status set, or request flow exists anywhere in this codebase. Consistent with the research: Shopify is the only platform of the four with this genuinely native; PrestaShop ships it off-by-default; Magento gates it behind the paid Adobe Commerce tier; WooCommerce lacks it entirely. Safe to treat as a real gap, not an urgent one.
+
+ +
+
Return shipping label generation
+ missing +
Depends entirely on the RMA flow above existing first — CarrierFulfillmentInterface already has the label-printing primitive (printLabel()) a return label would reuse, so the carrier-side plumbing isn't the blocker, the RMA request/approval model is.
+
+
+ +
+
+ 05 +

Order editing

+
+

Changing a placed order — and where every platform draws the line.

+ +
+
Editing guardrails keyed to fulfillment state
+ missing +
No line-item add/remove exists on a placed order at all today (unlike Shopify/Woo/PrestaShop, which all allow it up to some fulfillment-keyed cutoff, then force a return instead) — so there's no guardrail to speak of yet because there's no editing to guard. Whatever gets built here should key the cutoff to Shipment existing, per the pattern all four researched platforms converge on.
+
+ +
+
Editable shipping/billing address after placement
+ missing +
OrderAddress rows are snapshotted at creation (see Checkout survey, 02) and nothing in ManageOrder exposes editing them afterward — every platform researched treats address edits as lower-risk than line-item edits and allows them more freely; this codebase currently allows neither.
+
+ +
+
Tag editing on a placed order
+ have +
ManageOrder's edit_tags action already works — the one piece of native post-placement editing that exists today, via HasTags on the Order model.
+
+
+ +
+
+ 06 +

Notes & audit trail

+
+

The one thing every researched platform treats as non-negotiable.

+ +
+
Append-only change history (who changed what, when)
+ have +
Order already uses Spatie's LogsActivity trait — every save is recorded with a diff, same underlying mechanism already relied on elsewhere in this codebase (staff activity log, translation history). Structurally equivalent to PrestaShop's order_history table, just via a different package.
+
+ +
+
Internal staff notes, separate from system-generated log entries
+ missing +
The activity log above captures field changes automatically, but there's no free-text "leave a note for the next person" field — every platform researched has this as a distinct feed from the automatic history (Woo's Order Notes, Shopify's Timeline comments, Magento's Comments History), usually with a private-vs-customer-visible toggle. Nothing here yet.
+
+ +
+
Customer-visible note-to-customer, sent as a message
+ missing +
Depends on both the internal-notes feature above and a working mailer (01/02) — genuinely blocked on more foundational gaps, not just unbuilt on its own.
+
+
+ + + +