# Cart Admin Visibility `Modules\Core\Cart\Filament\Resources\CartResource` gives staff read-only visibility into customer/user carts in the Filament admin panel. Lunar itself ships no cart admin view at all — no Filament resource for `Cart`/`CartLine` exists anywhere in `lunarphp/lunar` or `lunarphp/core` — this is a from-scratch addition, not an extension of something Lunar half-built. See `docs/lunar.md`'s "Cart and Checkout" section for the underlying Lunar cart mechanics this resource reads from. --- ## Scope: only carts with a known customer or user `CartResource::getEloquentQuery()` filters to `Cart::whereNotNull('user_id')->orWhereNotNull('customer_id')` — an anonymous guest's session cart is excluded entirely. This was a deliberate call, not an oversight: an anonymous cart carries no identity a staff member could act on — no name, no email, nothing to follow up with — so listing every guest session cart would be noise, not a real admin capability. This does **not** mirror Shopify's admin (Shopify has no "all carts" view at all — only "Abandoned checkouts," gated on a shopper reaching checkout and entering contact info, a later/narrower stage than Lunar's `Cart`). Lunar's own `Cart` model already gets `user_id`/`customer_id` set the moment a shopper is authenticated (via `Lunar\Listeners\CartSessionAuthListener` on login), with no checkout step required — so scoping to "identifiable" here is broader than Shopify's equivalent, not a copy of it. --- ## "Abandoned" vs "Completed" — not `Cart::completed_at` `Lunar\Models\Cart::completed_at` is declared and cast (`'completed_at' => 'datetime'`) but **never actually written anywhere in Lunar core** — grep `vendor/lunarphp/core/src` for it; the only hits are the property declaration and the cast. It is not a real signal. The list page's tabs (`ListCarts::getTabs()`) instead key off whether the cart has a **placed** order: - **Abandoned** — mirrors `Cart::scopeActive()` exactly: no orders at all, or only orders still in draft (`placed_at IS NULL`). Default active tab on page load. - **Completed** — has at least one order with `placed_at IS NOT NULL`. ```php // Abandoned $query->active(); // Completed $query->whereHas('orders', fn ($query) => $query->whereNotNull('placed_at')); ``` There is deliberately **no "All" tab.** Every row shown is always scoped to one of the two states above — the list never runs an unfiltered `Cart::query()->get()` over the whole (potentially large) table. --- ## Why this scales fine at a large cart count Two things keep this cheap regardless of how many carts exist (10,000+): - **The list is always paginated.** Filament applies `LIMIT`/`OFFSET` to whichever tab's query is active — a page only ever fetches one page's worth of rows, never the whole table, "All" tab or not (and there is no "All" tab — see above). - **No per-row queries.** `lines_count`/`lines_sum_quantity` use Filament's built-in `->counts('lines')`/`->sum('lines', 'quantity')`, which fold into the same query as the rest of the list (one `LEFT JOIN`-based aggregate, not N separate lookups). There's no per-record `getStateUsing()` closure anywhere in this table doing its own query — that's the pattern to avoid if a future column needs derived data (see `Modules\Core\Catalog\ Services\ProductIndexer` for the general "compute once at index time / one aggregate query, never per-row" principle this project follows elsewhere). The one thing that **does** scan more rows as the cart count grows is `CartResource::getNavigationBadge()` (see below) — but it's a `COUNT(*)`, not a fetch, and runs once per admin page load, not once per cart row. --- ## Navigation badge — abandoned cart count ```php public static function getNavigationBadge(): ?string { return (string) static::getEloquentQuery()->active()->count(); } ``` Shows the number of abandoned carts (not all carts — a converted cart isn't something a staff member needs to keep noticing) next to "Carts" in the sidebar. `->count()` compiles to a single `SELECT COUNT(*) ...` — confirmed via query log — no rows are ever loaded just to render the badge. --- ## The view page runs the cart's full calculate pipeline — once `ViewCart::resolveRecord()` calls `$cart->calculate()` before rendering, since `CartLine`'s computed properties (`unitPrice`, `total`, etc.) and `Cart`'s own totals (`subTotal`, `total`, ...) are plain public properties populated as a side effect of that pipeline — never persisted, so a plain Eloquent-fetched `Cart` has them all `null`/unset (see `docs/lunar.md` Gotchas). This only runs on the single-record view page, not per row in the list table — running the full 5-step pipeline for every row of a paginated list would be needless cost for data the list doesn't display. --- ## Not built: staff editing a cart The resource is deliberately read-only (`canCreate()` returns `false`, no edit page registered). A cart is owned by the storefront's own add/update/remove flow (`CartSession`/`Cart::add()`/etc.) — hand-editing cart contents from the admin panel isn't a supported use case here.